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 *     Florent Guillaume
017 */
018package org.nuxeo.launcher;
019
020import java.io.Console;
021import java.io.File;
022import java.io.FileWriter;
023import java.io.FilenameFilter;
024import java.io.IOException;
025import java.io.StringWriter;
026import java.io.Writer;
027import java.net.SocketTimeoutException;
028import java.security.GeneralSecurityException;
029import java.util.ArrayList;
030import java.util.Arrays;
031import java.util.Collection;
032import java.util.Collections;
033import java.util.Date;
034import java.util.HashMap;
035import java.util.Iterator;
036import java.util.List;
037import java.util.Map;
038import java.util.StringTokenizer;
039import java.util.TreeSet;
040import java.util.concurrent.ExecutorService;
041import java.util.concurrent.Executors;
042import java.util.regex.Matcher;
043import java.util.regex.Pattern;
044
045import javax.xml.bind.JAXBContext;
046import javax.xml.bind.JAXBException;
047import javax.xml.bind.Marshaller;
048import javax.xml.parsers.DocumentBuilder;
049import javax.xml.parsers.DocumentBuilderFactory;
050
051import org.apache.commons.cli.CommandLine;
052import org.apache.commons.cli.CommandLineParser;
053import org.apache.commons.cli.DefaultParser;
054import org.apache.commons.cli.HelpFormatter;
055import org.apache.commons.cli.Option;
056import org.apache.commons.cli.OptionGroup;
057import org.apache.commons.cli.Options;
058import org.apache.commons.cli.ParseException;
059import org.apache.commons.codec.binary.Base64;
060import org.apache.commons.io.IOUtils;
061import org.apache.commons.lang.ArrayUtils;
062import org.apache.commons.lang.StringUtils;
063import org.apache.commons.lang.text.StrSubstitutor;
064import org.apache.commons.lang3.SystemUtils;
065import org.apache.commons.logging.Log;
066import org.apache.commons.logging.LogFactory;
067import org.apache.commons.logging.impl.SimpleLog;
068import org.artofsolving.jodconverter.process.MacProcessManager;
069import org.artofsolving.jodconverter.process.ProcessManager;
070import org.artofsolving.jodconverter.process.PureJavaProcessManager;
071import org.artofsolving.jodconverter.process.UnixProcessManager;
072import org.artofsolving.jodconverter.process.WindowsProcessManager;
073import org.artofsolving.jodconverter.util.PlatformUtils;
074import org.json.JSONException;
075import org.json.XML;
076import org.w3c.dom.Document;
077import org.w3c.dom.Element;
078import org.w3c.dom.NodeList;
079
080import org.nuxeo.common.Environment;
081import org.nuxeo.common.codec.Crypto;
082import org.nuxeo.common.codec.CryptoProperties;
083import org.nuxeo.connect.identity.LogicalInstanceIdentifier.NoCLID;
084import org.nuxeo.connect.update.LocalPackage;
085import org.nuxeo.connect.update.PackageException;
086import org.nuxeo.connect.update.Version;
087import org.nuxeo.launcher.config.ConfigurationException;
088import org.nuxeo.launcher.config.ConfigurationGenerator;
089import org.nuxeo.launcher.connect.ConnectBroker;
090import org.nuxeo.launcher.daemon.DaemonThreadFactory;
091import org.nuxeo.launcher.gui.NuxeoLauncherGUI;
092import org.nuxeo.launcher.info.CommandInfo;
093import org.nuxeo.launcher.info.CommandSetInfo;
094import org.nuxeo.launcher.info.ConfigurationInfo;
095import org.nuxeo.launcher.info.DistributionInfo;
096import org.nuxeo.launcher.info.InstanceInfo;
097import org.nuxeo.launcher.info.KeyValueInfo;
098import org.nuxeo.launcher.info.MessageInfo;
099import org.nuxeo.launcher.info.PackageInfo;
100import org.nuxeo.launcher.monitoring.StatusServletClient;
101import org.nuxeo.log4j.Log4JHelper;
102import org.nuxeo.log4j.ThreadedStreamGobbler;
103
104/**
105 * @author jcarsique
106 * @since 5.4.2
107 */
108public abstract class NuxeoLauncher {
109
110    /**
111     * @since 7.4
112     */
113    protected static final String OUTPUT_UNSET_VALUE = "<unset>";
114
115    /**
116     * @since 5.6
117     */
118    protected static final String OPTION_NODEPS = "nodeps";
119
120    private static final String OPTION_NODEPS_DESC = "Ignore package dependencies and constraints.";
121
122    /**
123     * @since 5.6
124     */
125    protected static final String OPTION_GUI = "gui";
126
127    private static final String OPTION_GUI_DESC = "Start graphical user interface (default is true on Windows and false on other platforms).";
128
129    /**
130     * @since 5.6
131     */
132    protected static final String OPTION_JSON = "json";
133
134    private static final String OPTION_JSON_DESC = "Output JSON for mp-* commands.";
135
136    /**
137     * @since 5.6
138     */
139    protected static final String OPTION_XML = "xml";
140
141    private static final String OPTION_XML_DESC = "Output XML for mp-* commands.";
142
143    /**
144     * @since 5.6
145     */
146    protected static final String OPTION_DEBUG = "debug";
147
148    private static final String OPTION_DEBUG_DESC = "Activate debug messages.\n"
149            + "<categories>: comma-separated Java categories to debug (default: \"org.nuxeo.launcher\").";
150
151    /**
152     * @since 7.4
153     */
154    private static final String OPTION_DEBUG_CATEGORY_ARG_NAME = "categories";
155
156    /**
157     * @since 5.6
158     */
159    protected static final String OPTION_DEBUG_CATEGORY = "dc";
160
161    private static final String OPTION_DEBUG_CATEGORY_DESC = "Deprecated: see categories on '--debug' option.";
162
163    /**
164     * @since 5.6
165     */
166    protected static final String OPTION_QUIET = "quiet";
167
168    private static final String OPTION_QUIET_DESC = "Suppress information messages.";
169
170    /**
171     * @since 5.6
172     */
173    protected static final String OPTION_HELP = "help";
174
175    private static final String OPTION_HELP_DESC = "Show detailed help.";
176
177    /**
178     * @since 5.6
179     */
180    protected static final String OPTION_RELAX = "relax";
181
182    private static final String OPTION_RELAX_DESC = "Allow relax constraint on current platform (default: "
183            + ConnectBroker.OPTION_RELAX_DEFAULT + ").";
184
185    /**
186     * @since 5.6
187     */
188    protected static final String OPTION_ACCEPT = "accept";
189
190    private static final String OPTION_ACCEPT_DESC = "Accept, refuse or ask confirmation for all changes (default: "
191            + ConnectBroker.OPTION_ACCEPT_DEFAULT + ").\n"
192            + "In non interactive mode, '--accept=true' also sets '--relax=true' if needed.";
193
194    /**
195     * @since 5.9.1
196     */
197    protected static final String OPTION_SNAPSHOT = "snapshot";
198
199    private static final String OPTION_SNAPSHOT_DESC = "Allow use of SNAPSHOT Marketplace packages.\n"
200            + "This option is implicit:\n" //
201            + "\t- on SNAPSHOT distributions (daily builds),\n"
202            + "\t- if the command explicitly requests a SNAPSHOT package.";
203
204    /**
205     * @since 5.9.1
206     */
207    protected static final String OPTION_FORCE = "force";
208
209    private static final String OPTION_FORCE_DESC = "Deprecated: use '--strict' option instead.";
210
211    /**
212     * @since 7.4
213     */
214    protected static final String OPTION_STRICT = "strict";
215
216    private static final String OPTION_STRICT_DESC = "Abort in error the start command when a component cannot "
217            + "be activated or if a server is already running.";
218
219    /**
220     * @since 5.6
221     */
222    protected static final String OPTION_HIDE_DEPRECATION = "hide-deprecation-warnings";
223
224    protected static final String OPTION_HIDE_DEPRECATION_DESC = "Hide deprecation warnings.";
225
226    /**
227     * @since 6.0
228     */
229    protected static final String OPTION_IGNORE_MISSING = "ignore-missing";
230
231    protected static final String OPTION_IGNORE_MISSING_DESC = "Ignore unknown packages on mp-add, mp-install and mp-set commands.";
232
233    /**
234     * @since 6.0
235     */
236    protected static final String OPTION_CLID = "clid";
237
238    private static final String OPTION_CLID_DESC = "Use the provided instance CLID file";
239
240    /**
241     * @since 7.4
242     */
243    protected static final String OPTION_ENCRYPT = "encrypt";
244
245    private static final String OPTION_ENCRYPT_ARG_NAME = "algorithm";
246
247    private static final String OPTION_ENCRYPT_DESC = String.format(
248            "Activate key value symmetric encryption.\n"
249                    + "The algorithm can be configured: <%s> is a cipher transformation of the form: \"algorithm/mode/padding\" or \"algorithm\".\n"
250                    + "Default value is \"%s\" (Advanced Encryption Standard, Electronic Cookbook Mode, PKCS5-style padding).",
251            OPTION_ENCRYPT, Crypto.DEFAULT_ALGO);
252
253    /**
254     * @since 7.4
255     */
256    protected static final String OPTION_SET = "set";
257
258    private static final String OPTION_SET_DESC = String.format(
259            "Set the value for a given key.\n"
260                    + "The value is stored in {{%s}} by default unless a template name is provided; if so, it is then stored in the template's {{%s}} file.\n"
261                    + "If the value is empty (''), then the property is unset.\n"
262                    + "This option is implicit if no '--get' or '--get-regexp' option is used and there are exactly two parameters (key value).",
263            ConfigurationGenerator.NUXEO_CONF, ConfigurationGenerator.NUXEO_DEFAULT_CONF);
264
265    /**
266     * @since 7.4
267     */
268    protected static final String OPTION_GET = "get";
269
270    private static final String OPTION_GET_DESC = "Get the value for a given key. Returns error code 6 if the key was not found.\n"
271            + "This option is implicit if '--set' option is not used and there are more or less than two parameters.";
272
273    /**
274     * @since 7.4
275     */
276    protected static final String OPTION_GET_REGEXP = "get-regexp";
277
278    private static final String OPTION_GET_REGEXP_DESC = "Get the value for all keys matching the given regular expression(s).";
279
280    // Fallback to avoid an error when the log dir is not initialized
281    static {
282        if (System.getProperty(Environment.NUXEO_LOG_DIR) == null) {
283            System.setProperty(Environment.NUXEO_LOG_DIR, ".");
284        }
285    }
286
287    /**
288     * @since 5.6
289     */
290    private static final String DEFAULT_NUXEO_CONTEXT_PATH = "/nuxeo";
291
292    static final Log log = LogFactory.getLog(NuxeoLauncher.class);
293
294    private static Options launcherOptions = null;
295
296    private static final String JAVA_OPTS_PROPERTY = "launcher.java.opts";
297
298    private static final String JAVA_OPTS_DEFAULT = "-Xms512m -Xmx1024m";
299
300    private static final String OVERRIDE_JAVA_TMPDIR_PARAM = "launcher.override.java.tmpdir";
301
302    protected boolean overrideJavaTmpDir;
303
304    private static final String START_MAX_WAIT_PARAM = "launcher.start.max.wait";
305
306    private static final String STOP_MAX_WAIT_PARAM = "launcher.stop.max.wait";
307
308    /**
309     * Default maximum time to wait for server startup summary in logs (in seconds).
310     */
311    private static final String START_MAX_WAIT_DEFAULT = "300";
312
313    /**
314     * Default maximum time to wait for effective stop (in seconds)
315     */
316    private static final String STOP_MAX_WAIT_DEFAULT = "60";
317
318    /**
319     * Number of try to cleanly stop server before killing process
320     */
321    private static final int STOP_NB_TRY = 5;
322
323    private static final int STOP_SECONDS_BEFORE_NEXT_TRY = 2;
324
325    private static final long STREAM_MAX_WAIT = 3000;
326
327    private static final String PACK_TOMCAT_CLASS = "org.nuxeo.runtime.deployment.preprocessor.PackWar";
328
329    private static final String PARAM_UPDATECENTER_DISABLED = "nuxeo.updatecenter.disabled";
330
331    private static final String[] COMMANDS_NO_GUI = { "configure", "mp-init", "mp-purge", "mp-add", "mp-install",
332            "mp-uninstall", "mp-request", "mp-remove", "mp-hotfix", "mp-upgrade", "mp-reset", "mp-list", "mp-listall",
333            "mp-update", "status", "showconf", "mp-show", "mp-set", "config", "encrypt", "decrypt", OPTION_HELP };
334
335    private static final String[] COMMANDS_NO_RUNNING_SERVER = { "pack", "mp-init", "mp-purge", "mp-add", "mp-install",
336            "mp-uninstall", "mp-request", "mp-remove", "mp-hotfix", "mp-upgrade", "mp-reset", "mp-update", "mp-set" };
337
338    /**
339     * @since 7.4
340     */
341    protected boolean commandRequiresNoRunningServer() {
342        return Arrays.asList(COMMANDS_NO_RUNNING_SERVER).contains(command);
343    }
344
345    /**
346     * @since 7.4
347     */
348    protected boolean commandRequiresNoGUI() {
349        return Arrays.asList(COMMANDS_NO_GUI).contains(command);
350    }
351
352    /**
353     * Program is running or service is OK.
354     *
355     * @since 5.7
356     */
357    public static final int STATUS_CODE_ON = 0;
358
359    /**
360     * Program is not running.
361     *
362     * @since 5.7
363     */
364    public static final int STATUS_CODE_OFF = 3;
365
366    /**
367     * Program or service status is unknown.
368     *
369     * @since 5.7
370     */
371    public static final int STATUS_CODE_UNKNOWN = 4;
372
373    /**
374     * @since 5.7
375     */
376    public static final int EXIT_CODE_OK = 0;
377
378    /**
379     * Generic or unspecified error.
380     *
381     * @since 5.7
382     */
383    public static final int EXIT_CODE_ERROR = 1;
384
385    /**
386     * Invalid or excess argument(s).
387     *
388     * @since 5.7
389     */
390    public static final int EXIT_CODE_INVALID = 2;
391
392    /**
393     * Unimplemented feature.
394     *
395     * @since 5.7
396     */
397    public static final int EXIT_CODE_UNIMPLEMENTED = 3;
398
399    /**
400     * User had insufficient privilege.
401     *
402     * @since 5.7
403     */
404    public static final int EXIT_CODE_UNAUTHORIZED = 4;
405
406    /**
407     * Program is not installed.
408     *
409     * @since 5.7
410     */
411    public static final int EXIT_CODE_NOT_INSTALLED = 5;
412
413    /**
414     * Program is not configured.
415     *
416     * @since 5.7
417     */
418    public static final int EXIT_CODE_NOT_CONFIGURED = 6;
419
420    /**
421     * Program is not running.
422     *
423     * @since 5.7
424     */
425    public static final int EXIT_CODE_NOT_RUNNING = 7;
426
427    private static final String OPTION_HELP_DESC_ENV = "\nENVIRONMENT VARIABLES\n"
428            + "        NUXEO_HOME\t\tPath to server root directory.\n" //
429            + "        NUXEO_CONF\t\tPath to {{nuxeo.conf}} file.\n"
430            + "        PATH\n"
431            + "\tJAVA\t\t\tPath to the {{java}} executable.\n"
432            + "        JAVA_HOME\t\tPath to the Java home directory. Can also be defined in {{nuxeo.conf}}.\n"
433            + "        JAVA_OPTS\t\tOptional values passed to the JVM. Can also be defined in {{nuxeo.conf}}.\n"
434            + "        REQUIRED_JAVA_VERSION\tNuxeo requirement on Java version.\n" //
435            + "\nJAVA USAGE\n"
436            + String.format("        java [-D%s=\"JVM options\"]"
437                    + " [-D%s=\"/path/to/nuxeo\"] [-D%s=\"/path/to/nuxeo.conf\"]"
438                    + " [-Djvmcheck=nofail] -jar \"path/to/nuxeo-launcher.jar\" \\\n"
439                    + "        \t[options] <command> [command parameters]\n\n", JAVA_OPTS_PROPERTY,
440                    Environment.NUXEO_HOME, ConfigurationGenerator.NUXEO_CONF)
441            + String.format("        %s\tParameters for the server JVM (default are %s).\n", JAVA_OPTS_PROPERTY,
442                    JAVA_OPTS_DEFAULT)
443            + String.format("        %s\t\tNuxeo server root path (default is parent of called script).\n",
444                    Environment.NUXEO_HOME)
445            + String.format("        %s\t\tPath to {{%1$s}} file (default is \"$NUXEO_HOME/bin/%1$s\").\n",
446                    ConfigurationGenerator.NUXEO_CONF)
447            + "        jvmcheck\t\tIf set to \"nofail\", ignore JVM version validation errors.\n";
448
449    private static final String OPTION_HELP_DESC_COMMANDS = "\nCOMMANDS\n"
450            + "        help\t\t\tPrint this message.\n"
451            + "        gui\t\t\tDeprecated: use '--gui' option instead.\n"
452            + "        start\t\t\tStart Nuxeo server in background, waiting for effective start. Useful for batch executions requiring the server being immediately available after the script returned.\n"
453            + "        stop\t\t\tStop any Nuxeo server started with the same {{nuxeo.conf}} file.\n"
454            + "        restart\t\t\tRestart Nuxeo server.\n"
455            + "        config\t\t\tGet and set template or global parameters.\n"
456            + "        encrypt\t\t\tOutput encrypted value for a given parameter.\n"
457            + "        decrypt\t\t\tOutput decrypted value for a given parameter.\n"
458            + "        configure\t\tConfigure Nuxeo server with parameters from {{nuxeo.conf}}.\n"
459            + "        wizard\t\t\tStart the wizard.\n"
460            + "        console\t\t\tStart Nuxeo server in a console mode. Ctrl-C will stop it.\n"
461            + "        status\t\t\tPrint server running status.\n"
462            + "        startbg\t\t\tStart Nuxeo server in background, without waiting for effective start. Useful for starting Nuxeo as a service.\n"
463            + "        restartbg\t\tRestart Nuxeo server with a call to \"startbg\" after \"stop\".\n"
464            + "        pack\t\t\tBuild a static archive.\n"
465            + "        showconf\t\tDisplay the instance configuration.\n"
466            + "        mp-list\t\t\tList local Marketplace packages.\n"
467            + "        mp-listall\t\tList all Marketplace Packages.\n"
468            + "        mp-init\t\t\tPre-cache Marketplace packages locally available in the distribution.\n"
469            + "        mp-update\t\tUpdate cache of marketplace packages list.\n"
470            + "        mp-add\t\t\tAdd Marketplace package(s) to local cache. You must provide the package file(s), name(s) or ID(s) as parameter.\n"
471            + "        mp-install\t\tRun Marketplace package installation. It is automatically called at startup if {{installAfterRestart.log}} file exists in data directory. Else you must provide the package file(s), name(s) or ID(s) as parameter.\n"
472            + "        mp-uninstall\t\tUninstall Marketplace package(s). You must provide the package name(s) or ID(s) as parameter (see \"mp-list\" command).\n"
473            + "        mp-remove\t\tRemove Marketplace package(s) from the local cache. You must provide the package name(s) or ID(s) as parameter (see \"mp-list\" command).\n"
474            + "        mp-reset\t\tReset all packages to DOWNLOADED state. May be useful after a manual server upgrade.\n"
475            + "        mp-set\t\t\tInstall a list of Marketplace Packages and remove those not in the list.\n"
476            + "        mp-request\t\tInstall and uninstall Marketplace Package(s) in one command. You must provide a *quoted* list of package names or IDs prefixed with + (install) or - (uninstall).\n"
477            + "        mp-purge\t\tUninstall and remove all packages from the local cache.\n"
478            + "        mp-hotfix\t\tInstall all the available hotfixes for the current platform (requires a registered instance).\n"
479            + "        mp-upgrade\t\tGet all the available upgrades for the Marketplace packages currently installed (requires a registered instance).\n"
480            + "        mp-show\t\t\tShow Marketplace package(s) information. You must provide the package file(s), name(s) or ID(s) as parameter.\n"
481            + "\nThe following commands are always executed in console/headless mode (no GUI): "
482            + "\"configure\", \"mp-init\", \"mp-purge\", \"mp-add\", \"mp-install\", \"mp-uninstall\", \"mp-request\", "
483            + "\"mp-remove\", \"mp-hotfix\", \"mp-upgrade\", \"mp-reset\", \"mp-list\", \"mp-listall\", \"mp-update\", "
484            + "\"status\", \"showconf\", \"mp-show\", \"mp-set\", \"config\", \"encrypt\", \"decrypt\", \"help\".\n"
485            + "\nThe following commands cannot be executed on a running server: \"pack\", \"mp-init\", \"mp-purge\", "
486            + "\"mp-add\", \"mp-install\", \"mp-uninstall\", \"mp-request\", \"mp-remove\", \"mp-hotfix\", \"mp-upgrade\", "
487            + "\"mp-reset\".";
488
489    private static final String OPTION_HELP_USAGE = "        nuxeoctl <command> [command parameters] [options]\n\n";
490
491    private static final String OPTION_HELP_HEADER = "SYNOPSIS\n"
492            + "        nuxeoctl encrypt [--encrypt <algorithm>] [<clearValue>..] [-d [<categories>]|-q]\n"
493            + "                Output encrypted value for <clearValue>.\n"
494            + "                If <clearValue> is not provided, it is read from stdin.\n\n"
495            + "        nuxeoctl decrypt '<cryptedValue>'.. [-d [<categories>]|-q]\n" //
496            + "                Output decrypted value for <cryptedValue>. The secret key is read from stdin.\n\n"
497            + "        nuxeoctl config [--encrypt [<algorithm>]] [--set [<template>]] [<key> <value>].. <key> [<value>] [-d [<categories>]|-q]\n"
498            + "                Set template or global parameters.\n"
499            + "                If <value> is not provided and the --set 'option' is used, then the value is read from stdin.\n\n"
500            + "        nuxeoctl config [--get] <key>.. [-d [<categories>]|-q]\n"
501            + "                Get value for the given key(s).\n\n"
502            + "        nuxeoctl config [--get-regexp] <regexp>.. [-d [<categories>]|-q]\n"
503            + "                Get value for the keys matching the given regular expression(s).\n\n"
504            + "        nuxeoctl [help|status|showconf] [-d [<categories>]|-q]\n\n"
505            + "        nuxeoctl [configure] [-d [<categories>]|-q|-hdw]\n\n"
506            + "        nuxeoctl [wizard] [-d [<categories>]|-q|--clid <arg>|--gui <true|false|yes|no>]\n\n"
507            + "        nuxeoctl [stop] [-d [<categories>]|-q|--gui <true|false|yes|no>]\n\n"
508            + "        nuxeoctl [start|restart|console|startbg|restartbg] [-d [<categories>]|-q|--clid <arg>|--gui <true|false|yes|no>|--strict|-hdw]\n\n"
509            + "        nuxeoctl [mp-show] [command parameters] [-d [<categories>]|-q|--clid <arg>|--xml|--json]\n\n"
510            + "        nuxeoctl [mp-list|mp-listall|mp-init|mp-update] [command parameters] [-d [<categories>]|-q|--clid <arg>|--xml|--json]\n\n"
511            + "        nuxeoctl [mp-reset|mp-purge|mp-hotfix|mp-upgrade] [command parameters] [-d [<categories>]|-q|--clid <arg>|--xml|--json|--accept <true|false|yes|no|ask>]\n\n"
512            + "        nuxeoctl [mp-add|mp-install|mp-uninstall|mp-remove|mp-set|mp-request] [command parameters] [-d [<categories>]|-q|--clid <arg>|--xml|--json|--nodeps|--relax <true|false|yes|no|ask>|--accept <true|false|yes|no|ask>|-s|-im]\n\n"
513            + "        nuxeoctl pack <target> [-d [<categories>]|-q]\n\n" + "OPTIONS";
514
515    private static final String OPTION_HELP_FOOTER = "\nSee online documentation \"ADMINDOC/nuxeoctl and Control Panel Usage\": https://doc.nuxeo.com/x/FwNc";
516
517    protected ConfigurationGenerator configurationGenerator;
518
519    public final ConfigurationGenerator getConfigurationGenerator() {
520        return configurationGenerator;
521    }
522
523    protected ProcessManager processManager;
524
525    protected Process nuxeoProcess;
526
527    private String processRegex;
528
529    protected String pid;
530
531    private ExecutorService executor = Executors.newSingleThreadExecutor(new DaemonThreadFactory("NuxeoProcessThread",
532            false));
533
534    private ShutdownThread shutdownHook;
535
536    protected String[] params;
537
538    protected String command;
539
540    public String getCommand() {
541        return command;
542    }
543
544    /**
545     * @since 7.4
546     */
547    public boolean commandIs(String aCommand) {
548        return StringUtils.equalsIgnoreCase(command, aCommand);
549    }
550
551    public CommandSetInfo cset = new CommandSetInfo();
552
553    private boolean useGui = false;
554
555    /**
556     * @since 5.5
557     */
558    public boolean isUsingGui() {
559        return useGui;
560    }
561
562    private boolean reloadConfiguration = false;
563
564    private int status = STATUS_CODE_UNKNOWN;
565
566    private int errorValue = EXIT_CODE_OK;
567
568    private StatusServletClient statusServletClient;
569
570    private static boolean quiet = false;
571
572    private static boolean debug = false;
573
574    private static boolean strict = false;
575
576    private boolean xmlOutput = false;
577
578    private boolean jsonOutput = false;
579
580    private ConnectBroker connectBroker = null;
581
582    private CommandLine cmdLine;
583
584    private boolean ignoreMissing = false;
585
586    /**
587     * @since 5.5
588     * @return true if quiet mode is active
589     */
590    public boolean isQuiet() {
591        return quiet;
592    }
593
594    private static Map<String, NuxeoLauncherGUI> guis;
595
596    /**
597     * @since 5.5
598     */
599    public NuxeoLauncherGUI getGUI() {
600        if (guis == null) {
601            return null;
602        }
603        return guis.get(configurationGenerator.getNuxeoConf().toString());
604    }
605
606    /**
607     * @since 5.5
608     */
609    public void setGUI(NuxeoLauncherGUI gui) {
610        if (guis == null) {
611            guis = new HashMap<>();
612        }
613        guis.put(configurationGenerator.getNuxeoConf().toString(), gui);
614    }
615
616    public NuxeoLauncher(ConfigurationGenerator configurationGenerator) {
617        this.configurationGenerator = configurationGenerator;
618        init();
619    }
620
621    /**
622     * @since 5.6
623     */
624    public void init() {
625        if (!configurationGenerator.init(true)) {
626            throw new IllegalStateException("Initialization failed");
627        }
628        statusServletClient = new StatusServletClient(configurationGenerator);
629        statusServletClient.setKey(configurationGenerator.getUserConfig().getProperty(Environment.SERVER_STATUS_KEY));
630        processManager = getOSProcessManager();
631        processRegex = "^(?!/bin/sh).*" + Pattern.quote(configurationGenerator.getNuxeoConf().getPath()) + ".*"
632                + Pattern.quote(getServerPrint()) + ".*$";
633
634        // Set OS-specific decorations
635        if (PlatformUtils.isMac()) {
636            System.setProperty("com.apple.mrj.application.apple.menu.about.name", "NuxeoCtl");
637        }
638    }
639
640    private ProcessManager getOSProcessManager() {
641        if (PlatformUtils.isLinux() || SystemUtils.IS_OS_AIX) {
642            UnixProcessManager unixProcessManager = new UnixProcessManager();
643            return unixProcessManager;
644        } else if (PlatformUtils.isMac()) {
645            return new MacProcessManager();
646        } else if (SystemUtils.IS_OS_SUN_OS) {
647            return new SolarisProcessManager();
648        } else if (PlatformUtils.isWindows()) {
649            WindowsProcessManager windowsProcessManager = new WindowsProcessManager();
650            return windowsProcessManager.isUsable() ? windowsProcessManager : new PureJavaProcessManager();
651        } else {
652            return new PureJavaProcessManager();
653        }
654    }
655
656    public static class SolarisProcessManager extends UnixProcessManager {
657
658        protected static final String SOLARIS_11 = "5.11";
659
660        protected static final String SOLARIS_10 = "5.10";
661
662        protected static final String[] SOLARIS_11_PS = { "/usr/bin/ps", "auxww" };
663
664        protected static final String[] SOLARIS_10_PS = { "/usr/ucb/ps", "auxww" };
665
666        protected static final Pattern PS_OUTPUT_LINE = Pattern.compile("^" + "[^\\s]+\\s+" // USER
667                + "([0-9]+)\\s+" // PID
668                + "[0-9.\\s]+" // %CPU %MEM SZ RSS (may be collapsed)
669                + "[^\\s]+\\s+" // TT (no starting digit)
670                + "[^\\s]+\\s+" // S
671                + "[^\\s]+\\s+" // START
672                + "[^\\s]+\\s+" // TIME
673                + "(.*)$" // COMMAND
674        );
675
676        protected String solarisVersion;
677
678        protected String getSolarisVersion() {
679            if (solarisVersion == null) {
680                List<String> lines;
681                try {
682                    lines = execute(new String[] { "/usr/bin/uname", "-r" });
683                } catch (IOException e) {
684                    log.debug(e.getMessage(), e);
685                    lines = Collections.emptyList();
686                }
687                if (lines.isEmpty()) {
688                    solarisVersion = "?";
689                } else {
690                    solarisVersion = lines.get(0).trim();
691                }
692            }
693            return solarisVersion;
694        }
695
696        @Override
697        protected String[] psCommand() {
698            if (SOLARIS_11.equals(getSolarisVersion())) {
699                return SOLARIS_11_PS;
700            }
701            return null;
702        }
703
704        protected Matcher getLineMatcher(String line) {
705            return PS_OUTPUT_LINE.matcher(line);
706        }
707
708        @Override
709        public String findPid(String regex) throws IOException {
710            if (SOLARIS_11.equals(getSolarisVersion())) {
711                Pattern commandPattern = Pattern.compile(regex);
712                for (String line : execute(psCommand())) {
713                    Matcher lineMatcher = getLineMatcher(line);
714                    if (lineMatcher.matches()) {
715                        String pid = lineMatcher.group(1);
716                        String command = lineMatcher.group(2);
717                        Matcher commandMatcher = commandPattern.matcher(command);
718                        if (commandMatcher.find()) {
719                            return pid;
720                        }
721                    }
722                }
723            } else {
724                log.debug("Unsupported Solaris version: " + solarisVersion);
725            }
726            return null;
727        }
728
729        protected List<String> execute(String... command) throws IOException {
730            Process process = new ProcessBuilder(command).start();
731            List<String> lines = IOUtils.readLines(process.getInputStream());
732            return lines;
733        }
734    }
735
736    /**
737     * Do not directly call this method without a call to {@link #checkNoRunningServer()}
738     *
739     * @see #doStart()
740     * @throws IOException In case of issue with process.
741     * @throws InterruptedException If any thread has interrupted the current thread.
742     */
743    protected void start(boolean logProcessOutput) throws IOException, InterruptedException {
744        List<String> startCommand = new ArrayList<>();
745        startCommand.add(getJavaExecutable().getPath());
746        startCommand.addAll(getJavaOptsPropertyAsList());
747        startCommand.add("-cp");
748        startCommand.add(getClassPath());
749        startCommand.addAll(getNuxeoProperties());
750        startCommand.addAll(getServerProperties());
751        setServerStartCommand(startCommand);
752        for (String param : params) {
753            startCommand.add(param);
754        }
755        ProcessBuilder pb = new ProcessBuilder(getOSCommand(startCommand));
756        pb.directory(configurationGenerator.getNuxeoHome());
757        log.debug("Server command: " + pb.command());
758        nuxeoProcess = pb.start();
759        Thread.sleep(1000);
760        boolean processExited = false;
761        // Check if process exited early
762        if (nuxeoProcess == null) {
763            log.error(String.format("Server start failed with command: %s", pb.command()));
764            if (PlatformUtils.isWindows() && configurationGenerator.getNuxeoHome().getPath().contains(" ")) {
765                // NXP-17679
766                log.error("The server path must not contain spaces under Windows.");
767            }
768            return;
769        }
770        try {
771            int exitValue = nuxeoProcess.exitValue();
772            if (exitValue != 0) {
773                log.error(String.format("Server start failed (%d).", exitValue));
774            }
775            processExited = true;
776        } catch (IllegalThreadStateException e) {
777            // Normal case
778        }
779        logProcessStreams(nuxeoProcess, processExited || logProcessOutput);
780        if (!processExited) {
781            if (getPid() != null) {
782                log.warn("Server started with process ID " + pid + ".");
783            } else {
784                log.warn("Sent server start command but could not get process ID.");
785            }
786        }
787    }
788
789    /**
790     * Gets the Java options with 'nuxeo.*' properties substituted. It enables usage of property like ${nuxeo.log.dir}
791     * inside JAVA_OPTS.
792     *
793     * @return the java options string.
794     */
795    protected String getJavaOptsProperty() {
796        String ret = System.getProperty(JAVA_OPTS_PROPERTY, JAVA_OPTS_DEFAULT);
797        ret = StrSubstitutor.replace(ret, configurationGenerator.getUserConfig());
798        return ret;
799    }
800
801    /**
802     * @return Java OPTS split on spaces followed by an even number of quotes (or zero)
803     * @since 7.10
804     */
805    protected List<String> getJavaOptsPropertyAsList() {
806        String javaOptsProperty = getJavaOptsProperty();
807        log.debug("JAVA OPTS:" + javaOptsProperty);
808        return Arrays.asList(javaOptsProperty.split("[ ]+(?=([^\"]*\"[^\"]*\")*[^\"]*$)"));
809    }
810
811    /**
812     * Check if some server is already running (from another thread) and throw a Runtime exception if it finds one. That
813     * method will work where {@link #isRunning()} won't.
814     *
815     * @throws IllegalThreadStateException Thrown if a server is already running.
816     */
817    public void checkNoRunningServer() throws IllegalStateException {
818        try {
819            String existingPid = getPid();
820            if (existingPid != null) {
821                errorValue = EXIT_CODE_OK;
822                throw new IllegalStateException("A server is running with process ID " + existingPid);
823            }
824        } catch (IOException e) {
825            log.warn("Could not check existing process: " + e.getMessage());
826        }
827    }
828
829    /**
830     * @return (since 5.5) Array list with created stream gobbler threads.
831     */
832    public ArrayList<ThreadedStreamGobbler> logProcessStreams(Process process, boolean logProcessOutput) {
833        ArrayList<ThreadedStreamGobbler> sgArray = new ArrayList<>();
834        ThreadedStreamGobbler inputSG, errorSG;
835        if (logProcessOutput) {
836            inputSG = new ThreadedStreamGobbler(process.getInputStream(), System.out);
837            errorSG = new ThreadedStreamGobbler(process.getErrorStream(), System.err);
838        } else {
839            inputSG = new ThreadedStreamGobbler(process.getInputStream(), SimpleLog.LOG_LEVEL_OFF);
840            errorSG = new ThreadedStreamGobbler(process.getErrorStream(), SimpleLog.LOG_LEVEL_OFF);
841        }
842        inputSG.start();
843        errorSG.start();
844        sgArray.add(inputSG);
845        sgArray.add(errorSG);
846        return sgArray;
847    }
848
849    protected abstract String getServerPrint();
850
851    /**
852     * Will wrap, if necessary, the command within a Shell command
853     *
854     * @param roughCommand Java command which will be run
855     * @return wrapped command depending on the OS
856     */
857    private List<String> getOSCommand(List<String> roughCommand) {
858        ArrayList<String> osCommand = new ArrayList<>();
859        if (PlatformUtils.isLinux() || PlatformUtils.isMac()) {
860            String linearizedCommand = new String();
861            for (String commandToken : roughCommand) {
862                if (StringUtils.isBlank(commandToken)) {
863                    continue;
864                }
865                if (commandToken.contains(" ")) {
866                    commandToken = commandToken.replaceAll(" ", "\\\\ ");
867                }
868                linearizedCommand += " " + commandToken;
869            }
870            osCommand.add("/bin/sh");
871            osCommand.add("-c");
872            osCommand.add(linearizedCommand);
873        } else {
874            for (String commandToken : roughCommand) {
875                if (StringUtils.isBlank(commandToken)) {
876                    continue;
877                }
878                osCommand.add(commandToken);
879            }
880        }
881        return osCommand;
882    }
883
884    protected abstract Collection<? extends String> getServerProperties();
885
886    protected abstract void setServerStartCommand(List<String> command);
887
888    private File getJavaExecutable() {
889        File javaExec = new File(System.getProperty("java.home"), "bin" + File.separator + "java");
890        return javaExec;
891    }
892
893    protected abstract String getClassPath();
894
895    /**
896     * @since 5.6
897     */
898    protected abstract String getShutdownClassPath();
899
900    protected Collection<? extends String> getNuxeoProperties() {
901        ArrayList<String> nuxeoProperties = new ArrayList<>();
902        nuxeoProperties.add(String.format("-D%s=%s", Environment.NUXEO_HOME, configurationGenerator.getNuxeoHome()
903                                                                                                   .getPath()));
904        nuxeoProperties.add(String.format("-D%s=%s", ConfigurationGenerator.NUXEO_CONF,
905                configurationGenerator.getNuxeoConf().getPath()));
906        nuxeoProperties.add(getNuxeoProperty(Environment.NUXEO_LOG_DIR));
907        nuxeoProperties.add(getNuxeoProperty(Environment.NUXEO_DATA_DIR));
908        nuxeoProperties.add(getNuxeoProperty(Environment.NUXEO_TMP_DIR));
909        if (!DEFAULT_NUXEO_CONTEXT_PATH.equals(configurationGenerator.getUserConfig().getProperty(
910                Environment.NUXEO_CONTEXT_PATH))) {
911            nuxeoProperties.add(getNuxeoProperty(Environment.NUXEO_CONTEXT_PATH));
912        }
913        if (overrideJavaTmpDir) {
914            nuxeoProperties.add("-Djava.io.tmpdir="
915                    + configurationGenerator.getUserConfig().getProperty(Environment.NUXEO_TMP_DIR));
916        }
917        return nuxeoProperties;
918    }
919
920    private String getNuxeoProperty(String property) {
921        return "-D" + property + "=" + configurationGenerator.getUserConfig().getProperty(property);
922    }
923
924    protected String addToClassPath(String cp, String filename) {
925        File classPathEntry = new File(configurationGenerator.getNuxeoHome(), filename);
926        if (!classPathEntry.exists()) {
927            classPathEntry = new File(filename);
928        }
929        if (!classPathEntry.exists()) {
930            throw new RuntimeException("Tried to add inexistent classpath entry: " + filename);
931        }
932        cp += System.getProperty("path.separator") + classPathEntry.getPath();
933        return cp;
934    }
935
936    /**
937     * @since 5.6
938     */
939    protected static void initParserOptions() {
940        if (launcherOptions == null) {
941            launcherOptions = new Options();
942            // help option
943            launcherOptions.addOption(Option.builder("h").longOpt(OPTION_HELP).desc(OPTION_HELP_DESC).build());
944            // Quiet option
945            launcherOptions.addOption(Option.builder("q").longOpt(OPTION_QUIET).desc(OPTION_QUIET_DESC).build());
946            { // Debug options (mutually exclusive)
947                OptionGroup debugOptions = new OptionGroup();
948                // Debug option
949                debugOptions.addOption(Option.builder("d")
950                                             .longOpt(OPTION_DEBUG)
951                                             .desc(OPTION_DEBUG_DESC)
952                                             .hasArgs()
953                                             .argName(OPTION_DEBUG_CATEGORY_ARG_NAME)
954                                             .optionalArg(true)
955                                             .valueSeparator(',')
956                                             .build());
957                // Debug category option
958                debugOptions.addOption(Option.builder(OPTION_DEBUG_CATEGORY)
959                                             .desc(OPTION_DEBUG_CATEGORY_DESC)
960                                             .hasArgs()
961                                             .argName(OPTION_DEBUG_CATEGORY_ARG_NAME)
962                                             .optionalArg(true)
963                                             .valueSeparator(',')
964                                             .build());
965                launcherOptions.addOptionGroup(debugOptions);
966            }
967            // For help output purpose only: that option is managed and swallowed by the nuxeoctl Shell script
968            launcherOptions.addOption(Option.builder()
969                                            .longOpt("debug-launcher")
970                                            .desc("Linux-only. Activate Java debugging mode on the Launcher.")
971                                            .build());
972            // Instance CLID option
973            launcherOptions.addOption(Option.builder().longOpt(OPTION_CLID).desc(OPTION_CLID_DESC).hasArg().build());
974            { // Output options (mutually exclusive)
975                OptionGroup outputOptions = new OptionGroup();
976                // XML option
977                outputOptions.addOption(Option.builder().longOpt(OPTION_XML).desc(OPTION_XML_DESC).build());
978                // JSON option
979                outputOptions.addOption(Option.builder().longOpt(OPTION_JSON).desc(OPTION_JSON_DESC).build());
980                launcherOptions.addOptionGroup(outputOptions);
981            }
982            // GUI option
983            launcherOptions.addOption(Option.builder()
984                                            .longOpt(OPTION_GUI)
985                                            .desc(OPTION_GUI_DESC)
986                                            .hasArg()
987                                            .argName("true|false|yes|no")
988                                            .build());
989            // Package management option
990            launcherOptions.addOption(Option.builder().longOpt(OPTION_NODEPS).desc(OPTION_NODEPS_DESC).build());
991            // Relax on target platform option
992            launcherOptions.addOption(Option.builder()
993                                            .longOpt(OPTION_RELAX)
994                                            .desc(OPTION_RELAX_DESC)
995                                            .hasArg()
996                                            .argName("true|false|yes|no|ask")
997                                            .build());
998            // Accept option
999            launcherOptions.addOption(Option.builder()
1000                                            .longOpt(OPTION_ACCEPT)
1001                                            .desc(OPTION_ACCEPT_DESC)
1002                                            .hasArg()
1003                                            .argName("true|false|yes|no|ask")
1004                                            .build());
1005            // Allow SNAPSHOT option
1006            launcherOptions.addOption(Option.builder("s").longOpt(OPTION_SNAPSHOT).desc(OPTION_SNAPSHOT_DESC).build());
1007            // Force option
1008            launcherOptions.addOption(Option.builder("f").longOpt(OPTION_FORCE).desc(OPTION_FORCE_DESC).build());
1009            // Strict option
1010            launcherOptions.addOption(Option.builder().longOpt(OPTION_STRICT).desc(OPTION_STRICT_DESC).build());
1011
1012            // Ignore missing option
1013            launcherOptions.addOption(Option.builder("im")
1014                                            .longOpt(OPTION_IGNORE_MISSING)
1015                                            .desc(OPTION_IGNORE_MISSING_DESC)
1016                                            .build());
1017            // Hide deprecation warnings option
1018            launcherOptions.addOption(Option.builder("hdw")
1019                                            .longOpt(OPTION_HIDE_DEPRECATION)
1020                                            .desc(OPTION_HIDE_DEPRECATION_DESC)
1021                                            .build());
1022            // Encrypt option
1023            launcherOptions.addOption(Option.builder()
1024                                            .longOpt(OPTION_ENCRYPT)
1025                                            .desc(OPTION_ENCRYPT_DESC)
1026                                            .hasArg()
1027                                            .argName(OPTION_ENCRYPT_ARG_NAME)
1028                                            .optionalArg(true)
1029                                            .build());
1030            { // Config options (mutually exclusive)
1031                OptionGroup configOptions = new OptionGroup();
1032                // Set option
1033                configOptions.addOption(Option.builder().longOpt(OPTION_SET).desc(OPTION_SET_DESC).build());
1034                configOptions.addOption(Option.builder().longOpt(OPTION_GET).desc(OPTION_GET_DESC).build());
1035                configOptions.addOption(Option.builder()
1036                                              .longOpt(OPTION_GET_REGEXP)
1037                                              .desc(OPTION_GET_REGEXP_DESC)
1038                                              .build());
1039                launcherOptions.addOptionGroup(configOptions);
1040            }
1041        }
1042    }
1043
1044    /**
1045     * @since 5.6
1046     */
1047    protected static CommandLine parseOptions(String[] args) throws ParseException {
1048        initParserOptions();
1049        CommandLineParser parser = new DefaultParser();
1050        CommandLine cmdLine = null;
1051        cmdLine = parser.parse(launcherOptions, args);
1052        if (cmdLine.hasOption(OPTION_HELP)) {
1053            cmdLine.getArgList().add(OPTION_HELP);
1054            setQuiet();
1055        } else if (cmdLine.getArgList().isEmpty()) {
1056            throw new ParseException("Missing command.");
1057        }
1058        // Common options to the Launcher and the ConfigurationGenerator
1059        if (cmdLine.hasOption(OPTION_QUIET) || cmdLine.hasOption(OPTION_XML) || cmdLine.hasOption(OPTION_JSON)) {
1060            setQuiet();
1061        }
1062        if (cmdLine.hasOption(OPTION_DEBUG)) {
1063            setDebug(cmdLine.getOptionValues(OPTION_DEBUG), "org.nuxeo.launcher");
1064        }
1065        if (cmdLine.hasOption(OPTION_DEBUG_CATEGORY)) {
1066            setDebug(cmdLine.getOptionValues(OPTION_DEBUG_CATEGORY), "org.nuxeo.launcher");
1067        }
1068        if (cmdLine.hasOption(OPTION_FORCE) || cmdLine.hasOption(OPTION_STRICT)) {
1069            setStrict(true);
1070        }
1071        return cmdLine;
1072    }
1073
1074    public static void main(String[] args) {
1075        NuxeoLauncher launcher = null;
1076        try {
1077            launcher = createLauncher(args);
1078            if (launcher.commandRequiresNoGUI()) {
1079                launcher.useGui = false;
1080            }
1081            if (launcher.useGui && launcher.getGUI() == null) {
1082                launcher.setGUI(new NuxeoLauncherGUI(launcher));
1083            }
1084            launch(launcher);
1085        } catch (ParseException e) {
1086            log.error("Invalid command line. " + e.getMessage());
1087            log.debug(e, e);
1088            printShortHelp();
1089            System.exit(launcher == null || launcher.errorValue == EXIT_CODE_OK ? EXIT_CODE_INVALID
1090                    : launcher.errorValue);
1091        } catch (IOException | PackageException | ConfigurationException | GeneralSecurityException e) {
1092            log.error(e.getMessage());
1093            log.debug(e, e);
1094            System.exit(launcher == null || launcher.errorValue == EXIT_CODE_OK ? EXIT_CODE_INVALID
1095                    : launcher.errorValue);
1096        } catch (Exception e) {
1097            log.error("Cannot execute command. " + e.getMessage());
1098            log.debug(e, e);
1099            System.exit(1);
1100        }
1101    }
1102
1103    /**
1104     * @since 5.5
1105     * @param launcher
1106     * @throws PackageException
1107     * @throws IOException
1108     * @throws ConfigurationException
1109     * @throws ParseException
1110     * @throws GeneralSecurityException
1111     */
1112    public static void launch(final NuxeoLauncher launcher) throws IOException, PackageException,
1113            ConfigurationException, ParseException, GeneralSecurityException {
1114        boolean commandSucceeded = true;
1115        if (launcher.commandIs(null)) {
1116            return;
1117        }
1118        if (launcher.commandRequiresNoRunningServer()) {
1119            launcher.checkNoRunningServer();
1120        }
1121        if (launcher.commandIs(OPTION_HELP)) {
1122            printLongHelp();
1123        } else if (launcher.commandIs("status")) {
1124            String statusMsg = launcher.status();
1125            launcher.errorValue = launcher.getStatus();
1126            if (!quiet) {
1127                log.warn(statusMsg);
1128                if (launcher.isStarted()) {
1129                    log.info("Go to " + launcher.getURL());
1130                    log.info(launcher.getStartupSummary());
1131                }
1132            }
1133        } else if (launcher.commandIs("startbg")) {
1134            commandSucceeded = launcher.doStart();
1135        } else if (launcher.commandIs("start")) {
1136            if (launcher.useGui) {
1137                launcher.getGUI().start();
1138            } else {
1139                commandSucceeded = launcher.doStartAndWait();
1140            }
1141        } else if (launcher.commandIs("console")) {
1142            launcher.executor.execute(new Runnable() {
1143                @Override
1144                public void run() {
1145                    launcher.addShutdownHook();
1146                    try {
1147                        if (!launcher.doStart(true)) {
1148                            launcher.removeShutdownHook();
1149                            System.exit(1);
1150                        } else if (!quiet) {
1151                            log.info("Go to " + launcher.getURL());
1152                        }
1153                    } catch (PackageException e) {
1154                        log.error("Could not initialize the packaging subsystem", e);
1155                        launcher.removeShutdownHook();
1156                        System.exit(EXIT_CODE_ERROR);
1157                    }
1158                }
1159            });
1160        } else if (launcher.commandIs("stop")) {
1161            if (launcher.useGui) {
1162                launcher.getGUI().stop();
1163            } else {
1164                launcher.stop();
1165            }
1166        } else if (launcher.commandIs("restartbg")) {
1167            launcher.stop();
1168            commandSucceeded = launcher.doStart();
1169        } else if (launcher.commandIs("restart")) {
1170            launcher.stop();
1171            commandSucceeded = launcher.doStartAndWait();
1172        } else if (launcher.commandIs("wizard")) {
1173            commandSucceeded = launcher.startWizard();
1174        } else if (launcher.commandIs("configure")) {
1175            launcher.configure();
1176        } else if (launcher.commandIs("pack")) {
1177            launcher.pack();
1178        } else if (launcher.commandIs("mp-list")) {
1179            launcher.pkgList();
1180        } else if (launcher.commandIs("mp-listall")) {
1181            launcher.pkgListAll();
1182        } else if (launcher.commandIs("mp-init")) {
1183            commandSucceeded = launcher.pkgInit();
1184        } else if (launcher.commandIs("mp-purge")) {
1185            commandSucceeded = launcher.pkgPurge();
1186        } else if (launcher.commandIs("mp-add")) {
1187            if (launcher.cmdLine.hasOption(OPTION_NODEPS)) {
1188                commandSucceeded = launcher.pkgAdd(launcher.params);
1189            } else {
1190                commandSucceeded = launcher.pkgRequest(Arrays.asList(launcher.params), null, null, null);
1191            }
1192        } else if (launcher.commandIs("mp-install")) {
1193            if (launcher.cmdLine.hasOption(OPTION_NODEPS)) {
1194                commandSucceeded = launcher.pkgInstall(launcher.params);
1195            } else {
1196                commandSucceeded = launcher.pkgRequest(null, Arrays.asList(launcher.params), null, null);
1197            }
1198        } else if (launcher.commandIs("mp-uninstall")) {
1199            if (launcher.cmdLine.hasOption(OPTION_NODEPS)) {
1200                commandSucceeded = launcher.pkgUninstall(launcher.params);
1201            } else {
1202                commandSucceeded = launcher.pkgRequest(null, null, Arrays.asList(launcher.params), null);
1203            }
1204        } else if (launcher.commandIs("mp-remove")) {
1205            if (launcher.cmdLine.hasOption(OPTION_NODEPS)) {
1206                commandSucceeded = launcher.pkgRemove(launcher.params);
1207            } else {
1208                commandSucceeded = launcher.pkgRequest(null, null, null, Arrays.asList(launcher.params));
1209            }
1210        } else if (launcher.commandIs("mp-request")) {
1211            if (launcher.cmdLine.hasOption(OPTION_NODEPS)) {
1212                throw new ParseException("The command mp-request is not available with the --nodeps option");
1213            } else {
1214                commandSucceeded = launcher.pkgCompoundRequest(Arrays.asList(launcher.params));
1215            }
1216        } else if (launcher.commandIs("mp-set")) {
1217            commandSucceeded = launcher.pkgSetRequest(Arrays.asList(launcher.params),
1218                    launcher.cmdLine.hasOption(OPTION_NODEPS));
1219        } else if (launcher.commandIs("mp-hotfix")) {
1220            commandSucceeded = launcher.pkgHotfix();
1221        } else if (launcher.commandIs("mp-upgrade")) {
1222            commandSucceeded = launcher.pkgUpgrade();
1223        } else if (launcher.commandIs("mp-reset")) {
1224            commandSucceeded = launcher.pkgReset();
1225        } else if (launcher.commandIs("mp-update")) {
1226            commandSucceeded = launcher.pkgRefreshCache();
1227        } else if (launcher.commandIs("showconf")) {
1228            launcher.showConfig();
1229        } else if (launcher.commandIs("mp-show")) {
1230            commandSucceeded = launcher.pkgShow(launcher.params);
1231        } else if (launcher.commandIs("encrypt")) {
1232            launcher.encrypt();
1233        } else if (launcher.commandIs("decrypt")) {
1234            launcher.decrypt();
1235        } else if (launcher.commandIs("config")) {
1236            launcher.config();
1237        } else {
1238            log.error("Unknown command " + launcher.command);
1239            printLongHelp();
1240            launcher.errorValue = EXIT_CODE_INVALID;
1241        }
1242        if (launcher.xmlOutput && launcher.command.startsWith("mp-")) {
1243            launcher.printXMLOutput();
1244        }
1245        commandSucceeded = commandSucceeded && launcher.errorValue == EXIT_CODE_OK;
1246        if (!commandSucceeded && !quiet || debug) {
1247            launcher.cset.log(commandSucceeded && debug);
1248        }
1249        if (!commandSucceeded) {
1250            System.exit(launcher.errorValue);
1251        }
1252    }
1253
1254    /**
1255     * @throws ConfigurationException
1256     * @throws GeneralSecurityException
1257     * @since 7.4
1258     */
1259    protected void encrypt() throws ConfigurationException, GeneralSecurityException {
1260        Crypto crypto = configurationGenerator.getCrypto();
1261        String algorithm = cmdLine.getOptionValue(OPTION_ENCRYPT, null);
1262        if (params.length == 0) {
1263            Console console = System.console();
1264            if (console == null) {
1265                errorValue = EXIT_CODE_INVALID;
1266                return;
1267            }
1268            params = new String[] { console.readLine("Please enter the value to encrypt: ") };
1269        }
1270        for (String strToEncrypt : params) {
1271            String encryptedString = crypto.encrypt(algorithm, strToEncrypt.getBytes());
1272            System.out.println(encryptedString);
1273        }
1274    }
1275
1276    /**
1277     * @throws ConfigurationException
1278     * @since 7.4
1279     */
1280    protected void decrypt() throws ConfigurationException {
1281        Crypto crypto = configurationGenerator.getCrypto();
1282        Console console = System.console();
1283        if (console == null) {
1284            errorValue = EXIT_CODE_ERROR;
1285            return;
1286        }
1287        if (!crypto.verifyKey(console.readPassword("Please enter the secret key: "))) {
1288            errorValue = EXIT_CODE_INVALID;
1289            return;
1290        }
1291        for (String strToDecrypt : params) {
1292            System.out.println(Crypto.getChars(crypto.decrypt(strToDecrypt)));
1293        }
1294    }
1295
1296    /**
1297     * @throws ConfigurationException
1298     * @throws IOException
1299     * @throws GeneralSecurityException
1300     * @since 7.4
1301     */
1302    protected void config() throws ConfigurationException, IOException, GeneralSecurityException {
1303        if (cmdLine.hasOption(OPTION_SET) || !cmdLine.hasOption(OPTION_GET) && !cmdLine.hasOption(OPTION_GET_REGEXP)
1304                && params.length == 2) {
1305            setConfigProperties();
1306        } else { // OPTION_GET || OPTION_GET_REGEXP || !OPTION_SET && params.length != 2
1307            getConfigProperties();
1308        }
1309    }
1310
1311    /**
1312     * @since 7.4
1313     */
1314    protected void getConfigProperties() {
1315        boolean isRegexp = cmdLine.hasOption(OPTION_GET_REGEXP);
1316        CryptoProperties userConfig = configurationGenerator.getUserConfig();
1317        List<String> keys;
1318        if (isRegexp) {
1319            keys = new ArrayList<>();
1320            for (Object key : userConfig.keySet()) {
1321                for (String param : params) {
1322                    Pattern pattern = Pattern.compile(param, Pattern.CASE_INSENSITIVE);
1323                    if (pattern.matcher((String) key).find()) {
1324                        keys.add((String) key);
1325                    }
1326                }
1327            }
1328            if (keys.isEmpty()) {
1329                errorValue = EXIT_CODE_NOT_CONFIGURED;
1330            }
1331        } else {
1332            keys = Arrays.asList(params);
1333        }
1334
1335        Crypto crypto = userConfig.getCrypto();
1336        boolean keyChecked = false; // Secret key is asked only once
1337        boolean raw = true;
1338        StringBuilder sb = new StringBuilder();
1339        final String newLine = System.getProperty("line.separator");
1340        for (String key : keys) {
1341            String value = userConfig.getProperty(key, raw);
1342            if (value == null) {
1343                errorValue = EXIT_CODE_NOT_CONFIGURED;
1344                sb.append(OUTPUT_UNSET_VALUE + newLine);
1345            } else {
1346                if (raw && !keyChecked && Crypto.isEncrypted(value)) {
1347                    keyChecked = true;
1348                    Console console = System.console();
1349                    if (console != null && crypto.verifyKey(console.readPassword("Please enter the secret key: "))) {
1350                        raw = false;
1351                        value = new String(crypto.decrypt(value));
1352                    } else {
1353                        errorValue = EXIT_CODE_ERROR;
1354                    }
1355                }
1356                if (isRegexp) {
1357                    sb.append(key + "=");
1358                }
1359                sb.append(value + newLine);
1360            }
1361        }
1362        System.out.println(sb.toString());
1363    }
1364
1365    /**
1366     * @throws IOException
1367     * @throws GeneralSecurityException
1368     * @since 7.4
1369     */
1370    protected void setConfigProperties() throws ConfigurationException, IOException, GeneralSecurityException {
1371        Crypto crypto = configurationGenerator.getCrypto();
1372        boolean doEncrypt = cmdLine.hasOption(OPTION_ENCRYPT);
1373        String algorithm = cmdLine.getOptionValue(OPTION_ENCRYPT, null);
1374        Map<String, String> changedParameters = new HashMap<>();
1375        for (Iterator<String> iterator = Arrays.asList(params).iterator(); iterator.hasNext();) {
1376            String key = iterator.next();
1377            String value;
1378            if (iterator.hasNext()) {
1379                value = iterator.next();
1380                if (doEncrypt) {
1381                    value = crypto.encrypt(algorithm, value.getBytes());
1382                } else if (Environment.CRYPT_KEY.equals(key) || Environment.CRYPT_KEYSTORE_PASS.equals(key)) {
1383                    value = Base64.encodeBase64String(value.getBytes());
1384                }
1385            } else {
1386                Console console = System.console();
1387                if (console == null) {
1388                    errorValue = EXIT_CODE_INVALID;
1389                    return;
1390                }
1391                if (doEncrypt) {
1392                    value = crypto.encrypt(algorithm,
1393                            Crypto.getBytes(console.readPassword("Please enter the value for %s: ", key)));
1394                } else if (Environment.CRYPT_KEY.equals(key) || Environment.CRYPT_KEYSTORE_PASS.equals(key)) {
1395                    value = Base64.encodeBase64String(Crypto.getBytes(console.readPassword(
1396                            "Please enter the value for %s: ", key)));
1397                } else {
1398                    value = console.readLine("Please enter the value for %s: ", key);
1399                }
1400            }
1401            changedParameters.put(key, value);
1402        }
1403        String template = cmdLine.getOptionValue(OPTION_SET);
1404        Map<String, String> oldValues;
1405        if (template == null) {
1406            oldValues = configurationGenerator.setProperties(changedParameters);
1407        } else {
1408            oldValues = configurationGenerator.setProperties(template, changedParameters);
1409        }
1410        log.debug("Old values: " + oldValues);
1411    }
1412
1413    /**
1414     * Since 5.5
1415     */
1416    protected boolean pack() {
1417        try {
1418            configurationGenerator.setProperty(PARAM_UPDATECENTER_DISABLED, "true");
1419            List<String> startCommand = new ArrayList<>();
1420            startCommand.add(getJavaExecutable().getPath());
1421            startCommand.addAll(Arrays.asList(getJavaOptsProperty().split(" ")));
1422            startCommand.add("-cp");
1423            String classpath = getClassPath();
1424            classpath = addToClassPath(classpath, "bin" + File.separator + "nuxeo-launcher.jar");
1425            classpath = getClassPath(classpath, configurationGenerator.getServerConfigurator().getServerLibDir());
1426            classpath = getClassPath(classpath, configurationGenerator.getServerConfigurator().getNuxeoLibDir());
1427            classpath = getClassPath(classpath, new File(configurationGenerator.getRuntimeHome(), "bundles"));
1428            startCommand.add(classpath);
1429            startCommand.addAll(getNuxeoProperties());
1430            if (configurationGenerator.isTomcat) {
1431                startCommand.add(PACK_TOMCAT_CLASS);
1432            } else {
1433                errorValue = EXIT_CODE_ERROR;
1434                return false;
1435            }
1436            startCommand.add(configurationGenerator.getRuntimeHome().getPath());
1437            for (String param : params) {
1438                startCommand.add(param);
1439            }
1440            ProcessBuilder pb = new ProcessBuilder(getOSCommand(startCommand));
1441            pb.directory(configurationGenerator.getNuxeoHome());
1442            log.debug("Pack command: " + pb.command());
1443            Process process = pb.start();
1444            ArrayList<ThreadedStreamGobbler> sgArray = logProcessStreams(process, true);
1445            Thread.sleep(100);
1446            process.waitFor();
1447            waitForProcessStreams(sgArray);
1448        } catch (IOException | InterruptedException e) {
1449            errorValue = EXIT_CODE_ERROR;
1450            log.error("Could not start process", e);
1451        } catch (ConfigurationException e) {
1452            errorValue = EXIT_CODE_ERROR;
1453            log.error(e);
1454        }
1455        return errorValue == EXIT_CODE_OK;
1456    }
1457
1458    protected boolean startWizard() throws PackageException {
1459        if (!configurationGenerator.getServerConfigurator().isWizardAvailable()) {
1460            log.error("Sorry, the wizard is not available within that server.");
1461            return false;
1462        }
1463        if (isRunning()) {
1464            log.error("Server already running. " + "Please stop it before calling \"wizard\" command "
1465                    + "or use the Admin Center instead of the wizard.");
1466            return false;
1467        }
1468        if (reloadConfiguration) {
1469            configurationGenerator = new ConfigurationGenerator(quiet, debug);
1470            configurationGenerator.init();
1471            reloadConfiguration = false;
1472        }
1473        configurationGenerator.getUserConfig().setProperty(ConfigurationGenerator.PARAM_WIZARD_DONE, "false");
1474        return doStart();
1475    }
1476
1477    /**
1478     * @throws PackageException
1479     * @see #doStartAndWait(boolean)
1480     */
1481    public boolean doStartAndWait() throws PackageException {
1482        boolean started = doStartAndWait(false);
1483        if (started && !quiet) {
1484            log.info("Go to " + getURL());
1485        }
1486        return started;
1487    }
1488
1489    /**
1490     * @see #stop(boolean)
1491     */
1492    public void stop() {
1493        stop(false);
1494    }
1495
1496    /**
1497     * Call {@link #doStart(boolean)} with false as parameter.
1498     *
1499     * @see #doStart(boolean)
1500     * @return true if the server started successfully
1501     * @throws PackageException
1502     */
1503    public boolean doStart() throws PackageException {
1504        boolean started = doStart(false);
1505        if (started && !quiet) {
1506            log.info("Go to " + getURL());
1507        }
1508        return started;
1509    }
1510
1511    /**
1512     * Whereas {@link #doStart()} considers the server as started when the process is running, {@link #doStartAndWait()}
1513     * waits for effective start by watching the logs
1514     *
1515     * @param logProcessOutput Must process output stream must be logged or not.
1516     * @return true if the server started successfully
1517     * @throws PackageException
1518     */
1519    public boolean doStartAndWait(boolean logProcessOutput) throws PackageException {
1520        boolean commandSucceeded = false;
1521        if (doStart(logProcessOutput)) {
1522            addShutdownHook();
1523            try {
1524                if (configurationGenerator.isWizardRequired() || waitForEffectiveStart()) {
1525                    commandSucceeded = true;
1526                }
1527                removeShutdownHook();
1528            } catch (InterruptedException e) {
1529                // do nothing
1530            }
1531        }
1532        return commandSucceeded;
1533    }
1534
1535    protected void removeShutdownHook() {
1536        try {
1537            Runtime.getRuntime().removeShutdownHook(shutdownHook);
1538            log.debug("Removed shutdown hook");
1539        } catch (IllegalStateException e) {
1540            // the virtual machine is already in the process of shutting down
1541        }
1542    }
1543
1544    /**
1545     * @return true if Nuxeo is ready
1546     * @throws InterruptedException
1547     */
1548    protected boolean waitForEffectiveStart() throws InterruptedException {
1549        long startTime = new Date().getTime();
1550        int startMaxWait = Integer.parseInt(configurationGenerator.getUserConfig().getProperty(START_MAX_WAIT_PARAM,
1551                getDefaultMaxWait()));
1552        log.debug("Will wait for effective start during " + startMaxWait + " seconds.");
1553        final StringBuilder startSummary = new StringBuilder();
1554        final String newLine = System.getProperty("line.separator");
1555        boolean isReady = false;
1556        long deltaTime = 0;
1557        // Wait for status servlet ready
1558        do {
1559            try {
1560                isReady = statusServletClient.init();
1561            } catch (SocketTimeoutException e) {
1562                if (!quiet) {
1563                    System.out.print(".");
1564                }
1565            }
1566            deltaTime = (new Date().getTime() - startTime) / 1000;
1567        } while (!isReady && deltaTime < startMaxWait && isRunning());
1568        isReady = false;
1569        // Wait for effective start reported from status servlet
1570        do {
1571            isReady = isStarted();
1572            if (!isReady) {
1573                if (!quiet) {
1574                    System.out.print(".");
1575                }
1576                Thread.sleep(1000);
1577            }
1578            deltaTime = (new Date().getTime() - startTime) / 1000;
1579        } while (!isReady && deltaTime < startMaxWait && isRunning());
1580        if (isReady) {
1581            startSummary.append(newLine + getStartupSummary());
1582            long duration = (new Date().getTime() - startTime) / 1000;
1583            startSummary.append("Started in "
1584                    + String.format("%dmin%02ds", new Long(duration / 60), new Long(duration % 60)));
1585            if (wasStartupFine()) {
1586                if (!quiet) {
1587                    System.out.println(startSummary);
1588                }
1589            } else {
1590                System.err.println(startSummary);
1591                if (strict) {
1592                    errorValue = EXIT_CODE_ERROR;
1593                    log.error("Shutting down because of unstarted component in strict mode...");
1594                    stop();
1595                    return false;
1596                }
1597            }
1598            return true;
1599        } else if (deltaTime >= startMaxWait) {
1600            if (!quiet) {
1601                System.out.println();
1602            }
1603            log.error("Starting process is taking too long - giving up.");
1604        }
1605        errorValue = EXIT_CODE_ERROR;
1606        return false;
1607    }
1608
1609    /**
1610     * Must be called after {@link #getStartupSummary()}
1611     *
1612     * @since 5.5
1613     * @return last detected status of running Nuxeo server
1614     */
1615    public boolean wasStartupFine() {
1616        return statusServletClient.isStartupFine();
1617    }
1618
1619    /**
1620     * @since 5.5
1621     * @return Nuxeo startup summary
1622     */
1623    public String getStartupSummary() {
1624        try {
1625            return statusServletClient.getStartupSummary();
1626        } catch (SocketTimeoutException e) {
1627            log.warn("Failed to contact Nuxeo for getting startup summary", e);
1628            return "";
1629        }
1630    }
1631
1632    /**
1633     * Starts the server in background.
1634     *
1635     * @return true if server successfully started
1636     * @throws PackageException
1637     */
1638    public boolean doStart(boolean logProcessOutput) throws PackageException {
1639        errorValue = EXIT_CODE_OK;
1640        boolean serverStarted = false;
1641        try {
1642            if (reloadConfiguration) {
1643                configurationGenerator = new ConfigurationGenerator(quiet, debug);
1644                configurationGenerator.init();
1645            } else {
1646                // Ensure reload on next start
1647                reloadConfiguration = true;
1648            }
1649            configure();
1650            configurationGenerator.verifyInstallation();
1651
1652            if (configurationGenerator.isWizardRequired()) {
1653                if (!configurationGenerator.isForceGeneration()) {
1654                    log.error("Cannot start setup wizard with " + ConfigurationGenerator.PARAM_FORCE_GENERATION
1655                            + "=false. Either set it to true or once, either set "
1656                            + ConfigurationGenerator.PARAM_WIZARD_DONE + "=true to skip the wizard.");
1657                    errorValue = EXIT_CODE_NOT_CONFIGURED;
1658                    return false;
1659                }
1660                String paramsStr = "";
1661                for (String param : params) {
1662                    paramsStr += " " + param;
1663                }
1664                System.setProperty(ConfigurationGenerator.PARAM_WIZARD_RESTART_PARAMS, paramsStr);
1665                configurationGenerator.prepareWizardStart();
1666            } else {
1667                configurationGenerator.cleanupPostWizard();
1668            }
1669
1670            log.debug("Check if install in progress...");
1671            if (configurationGenerator.isInstallInProgress()) {
1672                if (!getConnectBroker().executePending(configurationGenerator.getInstallFile(), true, true,
1673                        ignoreMissing)) {
1674                    errorValue = EXIT_CODE_ERROR;
1675                    log.error(String.format(
1676                            "Start interrupted due to failure on pending actions. You can resume with a new start;"
1677                                    + " or you can restore the file '%s', optionally using the '--%s' option.",
1678                            configurationGenerator.getInstallFile().getName(), OPTION_IGNORE_MISSING));
1679                    return false;
1680                }
1681
1682                // configuration will be reloaded, keep wizard value
1683                System.setProperty(
1684                        ConfigurationGenerator.PARAM_WIZARD_DONE,
1685                        configurationGenerator.getUserConfig().getProperty(ConfigurationGenerator.PARAM_WIZARD_DONE,
1686                                "true"));
1687                return doStart(logProcessOutput);
1688            }
1689
1690            start(logProcessOutput);
1691            serverStarted = isRunning();
1692            if (pid != null) {
1693                File pidFile = new File(configurationGenerator.getPidDir(), "nuxeo.pid");
1694                FileWriter writer = new FileWriter(pidFile);
1695                writer.write(pid);
1696                writer.close();
1697            }
1698        } catch (ConfigurationException e) {
1699            errorValue = EXIT_CODE_NOT_CONFIGURED;
1700            log.error("Could not run configuration: " + e.getMessage());
1701            log.debug(e, e);
1702        } catch (IOException e) {
1703            errorValue = EXIT_CODE_ERROR;
1704            log.error("Could not start process: " + e.getMessage());
1705            log.debug(e, e);
1706        } catch (InterruptedException e) {
1707            errorValue = EXIT_CODE_ERROR;
1708            log.error("Could not start process: " + e.getMessage());
1709            log.debug(e, e);
1710        } catch (IllegalStateException e) {
1711            if (strict) {
1712                // assume program is not configured because of http port binding
1713                // conflict
1714                errorValue = EXIT_CODE_NOT_CONFIGURED;
1715            }
1716            log.error(e.getMessage());
1717        }
1718        return serverStarted;
1719    }
1720
1721    /**
1722     * @since 5.6
1723     */
1724    protected void printXMLOutput() {
1725        try {
1726            JAXBContext jaxbContext = JAXBContext.newInstance(CommandSetInfo.class, CommandInfo.class,
1727                    PackageInfo.class, MessageInfo.class);
1728            printXMLOutput(jaxbContext, cset);
1729        } catch (JAXBException e) {
1730            log.error("Output serialization failed: " + e.getMessage(), e);
1731            errorValue = EXIT_CODE_NOT_RUNNING;
1732        }
1733    }
1734
1735    /**
1736     * @since 5.6
1737     */
1738    protected void printXMLOutput(JAXBContext jaxbContext, Object objectToOutput) {
1739        try {
1740            Writer xml = new StringWriter();
1741            Marshaller marshaller = jaxbContext.createMarshaller();
1742            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
1743            marshaller.marshal(objectToOutput, xml);
1744            if (!jsonOutput) {
1745                System.out.println(xml.toString());
1746            } else {
1747                try {
1748                    System.out.println(XML.toJSONObject(xml.toString()).toString(2));
1749                } catch (JSONException e) {
1750                    log.error(String.format("XML to JSON conversion failed: %s\nOutput was:\n%s", e.getMessage(),
1751                            xml.toString()));
1752                }
1753            }
1754        } catch (JAXBException e) {
1755            log.error("Output serialization failed: " + e.getMessage(), e);
1756            errorValue = EXIT_CODE_NOT_RUNNING;
1757        }
1758    }
1759
1760    /**
1761     * Stop stream gobblers contained in the given ArrayList
1762     *
1763     * @since 5.5
1764     * @see #logProcessStreams(Process, boolean)
1765     */
1766    public void waitForProcessStreams(ArrayList<ThreadedStreamGobbler> sgArray) {
1767        for (ThreadedStreamGobbler streamGobbler : sgArray) {
1768            try {
1769                streamGobbler.join(STREAM_MAX_WAIT);
1770            } catch (InterruptedException e) {
1771                streamGobbler.interrupt();
1772            }
1773        }
1774    }
1775
1776    /**
1777     * @since 5.5
1778     * @param classpath
1779     * @param baseDir
1780     * @return classpath with all jar files in baseDir
1781     * @throws IOException
1782     */
1783    protected String getClassPath(String classpath, File baseDir) throws IOException {
1784        File[] files = getFilename(baseDir, ".*");
1785        for (File file : files) {
1786            classpath += System.getProperty("path.separator") + file.getPath();
1787        }
1788        return classpath;
1789    }
1790
1791    /**
1792     * @since 5.5
1793     * @param baseDir
1794     * @param filePattern
1795     * @return filename matching filePattern in baseDir
1796     */
1797    protected File[] getFilename(File baseDir, final String filePattern) {
1798        File[] files = baseDir.listFiles(new FilenameFilter() {
1799            @Override
1800            public boolean accept(File basedir, String filename) {
1801                return filename.matches(filePattern + "(-[0-9].*)?\\.jar");
1802            }
1803        });
1804        return files;
1805    }
1806
1807    protected class ShutdownThread extends Thread {
1808
1809        private NuxeoLauncher launcher;
1810
1811        public ShutdownThread(NuxeoLauncher launcher) {
1812            super();
1813            this.launcher = launcher;
1814        }
1815
1816        @Override
1817        public void run() {
1818            log.debug("Shutting down...");
1819            if (launcher.isRunning()) {
1820                launcher.stop();
1821            }
1822            log.debug("Shutdown complete.");
1823        }
1824    }
1825
1826    protected void addShutdownHook() {
1827        log.debug("Add shutdown hook");
1828        shutdownHook = new ShutdownThread(this);
1829        Runtime.getRuntime().addShutdownHook(shutdownHook);
1830    }
1831
1832    /**
1833     * Stops the server. Will try to call specific class for a clean stop, retry, waiting between each try, then kill
1834     * the process if still running.
1835     */
1836    public void stop(boolean logProcessOutput) {
1837        long startTime = new Date().getTime();
1838        long deltaTime;
1839        try {
1840            if (!isRunning()) {
1841                log.warn("Server is not running.");
1842                return;
1843            }
1844            if (!quiet) {
1845                System.out.print("\nStopping server...");
1846            }
1847            int nbTry = 0;
1848            boolean retry = false;
1849            int stopMaxWait = Integer.parseInt(configurationGenerator.getUserConfig().getProperty(STOP_MAX_WAIT_PARAM,
1850                    STOP_MAX_WAIT_DEFAULT));
1851            do {
1852                List<String> stopCommand = new ArrayList<>();
1853                stopCommand.add(getJavaExecutable().getPath());
1854                stopCommand.add("-cp");
1855                stopCommand.add(getShutdownClassPath());
1856                stopCommand.addAll(getNuxeoProperties());
1857                stopCommand.addAll(getServerProperties());
1858                setServerStopCommand(stopCommand);
1859                for (String param : params) {
1860                    stopCommand.add(param);
1861                }
1862                ProcessBuilder pb = new ProcessBuilder(getOSCommand(stopCommand));
1863                pb.directory(configurationGenerator.getNuxeoHome());
1864                // pb = pb.redirectErrorStream(true);
1865                log.debug("Server command: " + pb.command());
1866                try {
1867                    Process stopProcess = pb.start();
1868                    ArrayList<ThreadedStreamGobbler> sgArray = logProcessStreams(stopProcess, logProcessOutput);
1869                    stopProcess.waitFor();
1870                    waitForProcessStreams(sgArray);
1871                    boolean wait = true;
1872                    while (wait) {
1873                        try {
1874                            if (stopProcess.exitValue() == 0) {
1875                                // Successful call for server stop
1876                                retry = false;
1877                            } else {
1878                                // Failed to call for server stop
1879                                retry = ++nbTry < STOP_NB_TRY;
1880                                if (!quiet) {
1881                                    System.out.print(".");
1882                                }
1883                                Thread.sleep(STOP_SECONDS_BEFORE_NEXT_TRY * 1000);
1884                            }
1885                            wait = false;
1886                        } catch (IllegalThreadStateException e) {
1887                            // Stop call is still running
1888                            wait = true;
1889                            if (!quiet) {
1890                                System.out.print(".");
1891                            }
1892                            Thread.sleep(1000);
1893                        }
1894                    }
1895                    // Exit if there's no way to check for server stop
1896                    if (!processManager.canFindPid()) {
1897                        log.warn("Can't check server status on your OS.");
1898                        return;
1899                    }
1900                    // Wait a few seconds for effective stop
1901                    deltaTime = 0;
1902                    do {
1903                        if (!quiet) {
1904                            System.out.print(".");
1905                        }
1906                        Thread.sleep(1000);
1907                        deltaTime = (new Date().getTime() - startTime) / 1000;
1908                    } while (!retry && getPid() != null && deltaTime < stopMaxWait);
1909                } catch (InterruptedException e) {
1910                    log.error(e);
1911                }
1912            } while (retry);
1913            if (getPid() == null) {
1914                log.warn("Server stopped.");
1915            } else {
1916                log.info("No answer from server, try to kill process " + pid + "...");
1917                processManager.kill(nuxeoProcess, pid);
1918                if (getPid() == null) {
1919                    log.warn("Server forcibly stopped.");
1920                }
1921            }
1922        } catch (IOException e) {
1923            log.error("Could not manage process!", e);
1924        }
1925    }
1926
1927    protected abstract void setServerStopCommand(List<String> command);
1928
1929    private String getPid() throws IOException {
1930        pid = processManager.findPid(processRegex);
1931        log.debug("regexp: " + processRegex + " pid:" + pid);
1932        return pid;
1933    }
1934
1935    /**
1936     * Configure the server after checking installation
1937     *
1938     * @throws ConfigurationException If an installation error is detected or if configuration fails
1939     */
1940    public void configure() throws ConfigurationException {
1941        try {
1942            checkNoRunningServer();
1943            configurationGenerator.checkJavaVersion();
1944            configurationGenerator.run();
1945            overrideJavaTmpDir = Boolean.parseBoolean(configurationGenerator.getUserConfig().getProperty(
1946                    OVERRIDE_JAVA_TMPDIR_PARAM, "true"));
1947        } catch (ConfigurationException e) {
1948            errorValue = EXIT_CODE_NOT_CONFIGURED;
1949            throw e;
1950        }
1951    }
1952
1953    /**
1954     * @return Default max wait depending on server (ie JBoss takes much more time than Tomcat)
1955     */
1956    private String getDefaultMaxWait() {
1957        return START_MAX_WAIT_DEFAULT;
1958    }
1959
1960    /**
1961     * Return process status (running or not) as String, depending on OS capability to manage processes. Set status
1962     * value following "http://refspecs.freestandards.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html"
1963     *
1964     * @see #getStatus()
1965     */
1966    public String status() {
1967        try {
1968            if (processManager instanceof PureJavaProcessManager) {
1969                status = STATUS_CODE_UNKNOWN;
1970                return "Can't check server status on your OS.";
1971            }
1972            if (getPid() == null) {
1973                status = STATUS_CODE_OFF;
1974                return "Server is not running.";
1975            } else {
1976                status = STATUS_CODE_ON;
1977                return "Server is running with process ID " + getPid() + ".";
1978            }
1979        } catch (IOException e) {
1980            status = STATUS_CODE_UNKNOWN;
1981            return "Could not check existing process (" + e.getMessage() + ").";
1982        }
1983    }
1984
1985    /**
1986     * Last status value set by {@link #status()}.
1987     */
1988    public int getStatus() {
1989        return status;
1990    }
1991
1992    /**
1993     * Last error value set by any method. Exit code values are following the Linux Standard Base Core Specification
1994     * 4.1.
1995     */
1996    public int getErrorValue() {
1997        return errorValue;
1998    }
1999
2000    /**
2001     * @throws ParseException
2002     * @return a NuxeoLauncher instance specific to current server ( Tomcat or Jetty).
2003     * @throws ConfigurationException If server cannot be identified
2004     * @since 5.5
2005     */
2006    public static NuxeoLauncher createLauncher(String[] args) throws ConfigurationException, ParseException {
2007        CommandLine cmdLine = parseOptions(args);
2008        ConfigurationGenerator cg = new ConfigurationGenerator(quiet, debug);
2009        if (cmdLine.hasOption(OPTION_HIDE_DEPRECATION)) {
2010            cg.hideDeprecationWarnings(true);
2011        }
2012        NuxeoLauncher launcher;
2013        if (cg.isJetty) {
2014            launcher = new NuxeoJettyLauncher(cg);
2015        } else if (cg.isTomcat) {
2016            launcher = new NuxeoTomcatLauncher(cg);
2017        } else {
2018            throw new ConfigurationException("Unknown server!");
2019        }
2020        launcher.setArgs(cmdLine);
2021        return launcher;
2022    }
2023
2024    /**
2025     * Sets from program arguments the launcher command and additional parameters.
2026     *
2027     * @param cmdLine Program arguments; may be used by launcher implementation. Must not be null or empty.
2028     * @throws ConfigurationException
2029     */
2030    private void setArgs(CommandLine cmdLine) throws ConfigurationException {
2031        this.cmdLine = cmdLine;
2032        extractCommandAndParams(cmdLine.getArgs());
2033        // Use GUI?
2034        if (cmdLine.hasOption(OPTION_GUI)) {
2035            useGui = Boolean.valueOf(ConnectBroker.parseAnswer(cmdLine.getOptionValue(OPTION_GUI)));
2036            log.debug("GUI: " + cmdLine.getOptionValue(OPTION_GUI) + " -> " + new Boolean(useGui).toString());
2037        } else if (OPTION_GUI.equalsIgnoreCase(command)) {
2038            useGui = true;
2039            // Shift params and extract command if there is one
2040            extractCommandAndParams(params);
2041        } else {
2042            if (PlatformUtils.isWindows()) {
2043                useGui = true;
2044                log.debug("GUI: option not set - platform is Windows -> start GUI");
2045            } else {
2046                useGui = false;
2047                log.debug("GUI: option not set - platform is not Windows -> do not start GUI");
2048            }
2049        }
2050        // Output format
2051        if (cmdLine.hasOption(OPTION_XML)) {
2052            setXMLOutput();
2053        }
2054        if (cmdLine.hasOption(OPTION_JSON)) {
2055            setJSONOutput();
2056        }
2057        if (cmdLine.hasOption(OPTION_CLID)) {
2058            try {
2059                getConnectBroker().setCLID(cmdLine.getOptionValue(OPTION_CLID));
2060            } catch (NoCLID | IOException | PackageException e) {
2061                throw new ConfigurationException(e);
2062            }
2063        }
2064        if (cmdLine.hasOption(OPTION_IGNORE_MISSING)) {
2065            ignoreMissing = true;
2066        }
2067    }
2068
2069    private void extractCommandAndParams(String[] args) {
2070        if (args.length > 0) {
2071            command = args[0];
2072            log.debug("Launcher command: " + command);
2073            // Command parameters
2074            if (args.length > 1) {
2075                params = Arrays.copyOfRange(args, 1, args.length);
2076                if (log.isDebugEnabled()) {
2077                    log.debug("Command parameters: " + ArrayUtils.toString(params));
2078                }
2079            } else {
2080                params = new String[0];
2081            }
2082        } else {
2083            command = null;
2084        }
2085    }
2086
2087    /**
2088     * Set launcher in quiet mode
2089     *
2090     * @since 5.5
2091     */
2092    protected static void setQuiet() {
2093        quiet = true;
2094        Log4JHelper.setQuiet(Log4JHelper.CONSOLE_APPENDER_NAME);
2095    }
2096
2097    /**
2098     * @param categories Root categories to switch DEBUG on.
2099     * @since 7.4
2100     */
2101    protected static void setDebug(String[] categories, String defaultCategory) {
2102        debug = true;
2103        if (categories == null) {
2104            categories = new String[] { defaultCategory };
2105        }
2106        Log4JHelper.setDebug(categories, true, true, new String[] { Log4JHelper.CONSOLE_APPENDER_NAME, "FILE" });
2107    }
2108
2109    /**
2110     * @param categories Root categories to switch DEBUG on.
2111     * @since 5.6
2112     */
2113    protected static void setDebug(String categories) {
2114        setDebug(categories, true);
2115    }
2116
2117    /**
2118     * @param categories Root categories to switch DEBUG on or off
2119     * @param activateDebug Set DEBUG on or off.
2120     * @since 5.6
2121     */
2122    protected static void setDebug(String categories, boolean activateDebug) {
2123        debug = activateDebug;
2124        Log4JHelper.setDebug(categories, activateDebug, true,
2125                new String[] { Log4JHelper.CONSOLE_APPENDER_NAME, "FILE" });
2126    }
2127
2128    /**
2129     * @param activateDebug if true, will activate the DEBUG logs
2130     * @since 5.5
2131     */
2132    protected static void setDebug(boolean activateDebug) {
2133        setDebug("org.nuxeo", activateDebug);
2134    }
2135
2136    /**
2137     * @param isStrict if {@code true}, set the launcher strict option
2138     * @since 7.4
2139     * @see #OPTION_STRICT_DESC
2140     */
2141    protected static void setStrict(boolean isStrict) {
2142        strict = isStrict;
2143    }
2144
2145    protected void setXMLOutput() {
2146        xmlOutput = true;
2147    }
2148
2149    protected void setJSONOutput() {
2150        jsonOutput = true;
2151        setXMLOutput();
2152    }
2153
2154    public static void printShortHelp() {
2155        System.out.println();
2156        HelpFormatter help = new HelpFormatter();
2157        help.setSyntaxPrefix("USAGE\n");
2158        help.setOptionComparator(null);
2159        help.setWidth(1000);
2160        help.printHelp(OPTION_HELP_USAGE, "OPTIONS", launcherOptions, null);
2161        System.out.println(OPTION_HELP_DESC_COMMANDS);
2162    }
2163
2164    public static void printLongHelp() {
2165        System.out.println();
2166        HelpFormatter help = new HelpFormatter();
2167        help.setSyntaxPrefix("USAGE\n");
2168        help.setOptionComparator(null);
2169        help.setWidth(1000);
2170        help.printHelp(OPTION_HELP_USAGE, OPTION_HELP_HEADER, launcherOptions, null);
2171        System.out.println(OPTION_HELP_DESC_ENV);
2172        System.out.println(OPTION_HELP_DESC_COMMANDS);
2173        System.out.println(OPTION_HELP_FOOTER);
2174    }
2175
2176    /**
2177     * Work best with current nuxeoProcess. If nuxeoProcess is null or has exited, then will try to get process ID (so,
2178     * result in that case depends on OS capabilities).
2179     *
2180     * @return true if current process is running or if a running PID is found
2181     */
2182    public boolean isRunning() {
2183        if (nuxeoProcess != null) {
2184            try {
2185                nuxeoProcess.exitValue();
2186                // Previous process has exited
2187                nuxeoProcess = null;
2188            } catch (IllegalThreadStateException exception) {
2189                return true;
2190            }
2191        }
2192        try {
2193            return (getPid() != null);
2194        } catch (IOException e) {
2195            log.error(e);
2196            return false;
2197        }
2198    }
2199
2200    /**
2201     * @since 5.5
2202     * @return true if Nuxeo finished starting
2203     */
2204    public boolean isStarted() {
2205        boolean isStarted;
2206        if (configurationGenerator.isWizardRequired()) {
2207            isStarted = isRunning();
2208        } else {
2209            try {
2210                isStarted = isRunning() && statusServletClient.isStarted();
2211            } catch (SocketTimeoutException e) {
2212                isStarted = false;
2213            }
2214        }
2215        return isStarted;
2216    }
2217
2218    /**
2219     * @return Server log file
2220     */
2221    public File getLogFile() {
2222        return new File(configurationGenerator.getLogDir(), "server.log");
2223    }
2224
2225    /**
2226     * @return Server URL
2227     */
2228    public String getURL() {
2229        return configurationGenerator.getUserConfig().getProperty(ConfigurationGenerator.PARAM_NUXEO_URL);
2230    }
2231
2232    protected ConnectBroker getConnectBroker() throws IOException, PackageException {
2233        if (connectBroker == null) {
2234            connectBroker = new ConnectBroker(configurationGenerator.getEnv());
2235            if (cmdLine.hasOption(OPTION_ACCEPT)) {
2236                connectBroker.setAccept(cmdLine.getOptionValue(OPTION_ACCEPT));
2237            }
2238            if (cmdLine.hasOption(OPTION_RELAX)) {
2239                connectBroker.setRelax(cmdLine.getOptionValue(OPTION_RELAX));
2240            }
2241            if (cmdLine.hasOption(OPTION_SNAPSHOT) || isSNAPSHOTDistribution()) {
2242                connectBroker.setAllowSNAPSHOT(true);
2243            }
2244            cset = connectBroker.getCommandSet();
2245        }
2246        return connectBroker;
2247    }
2248
2249    /**
2250     * @since 5.9.1
2251     */
2252    private boolean isSNAPSHOTDistribution() {
2253        return new Version(getDistributionInfo().version).isSnapshot();
2254    }
2255
2256    /**
2257     * List all local packages.
2258     *
2259     * @throws IOException
2260     * @throws PackageException
2261     */
2262    protected void pkgList() throws IOException, PackageException {
2263        getConnectBroker().listPending(configurationGenerator.getInstallFile());
2264        getConnectBroker().pkgList();
2265    }
2266
2267    /**
2268     * List all packages including remote ones.
2269     *
2270     * @since 5.6
2271     * @throws IOException
2272     * @throws PackageException
2273     */
2274    protected void pkgListAll() throws IOException, PackageException {
2275        getConnectBroker().listPending(configurationGenerator.getInstallFile());
2276        getConnectBroker().pkgListAll();
2277    }
2278
2279    protected boolean pkgAdd(String[] pkgNames) throws IOException, PackageException {
2280        boolean cmdOK = getConnectBroker().pkgAdd(Arrays.asList(pkgNames), ignoreMissing);
2281        if (!cmdOK) {
2282            errorValue = EXIT_CODE_ERROR;
2283        }
2284        return cmdOK;
2285    }
2286
2287    protected boolean pkgInstall(String[] pkgIDs) throws IOException, PackageException {
2288        boolean cmdOK = true;
2289        if (configurationGenerator.isInstallInProgress()) {
2290            cmdOK = getConnectBroker().executePending(configurationGenerator.getInstallFile(), true,
2291                    !cmdLine.hasOption(OPTION_NODEPS), ignoreMissing);
2292        }
2293        cmdOK = cmdOK && getConnectBroker().pkgInstall(Arrays.asList(pkgIDs), ignoreMissing);
2294        if (!cmdOK) {
2295            errorValue = EXIT_CODE_ERROR;
2296        }
2297        return cmdOK;
2298    }
2299
2300    protected boolean pkgUninstall(String[] pkgIDs) throws IOException, PackageException {
2301        boolean cmdOK = getConnectBroker().pkgUninstall(Arrays.asList(pkgIDs));
2302        if (!cmdOK) {
2303            errorValue = EXIT_CODE_ERROR;
2304        }
2305        return cmdOK;
2306    }
2307
2308    protected boolean pkgRemove(String[] pkgIDs) throws IOException, PackageException {
2309        boolean cmdOK = getConnectBroker().pkgRemove(Arrays.asList(pkgIDs));
2310        if (!cmdOK) {
2311            errorValue = EXIT_CODE_ERROR;
2312        }
2313        return cmdOK;
2314    }
2315
2316    protected boolean pkgReset() throws IOException, PackageException {
2317        boolean cmdOK = getConnectBroker().pkgReset();
2318        if (!cmdOK) {
2319            errorValue = EXIT_CODE_ERROR;
2320        }
2321        return cmdOK;
2322    }
2323
2324    /**
2325     * @since 5.6
2326     */
2327    protected void printInstanceXMLOutput(InstanceInfo instance) {
2328        try {
2329            JAXBContext jaxbContext = JAXBContext.newInstance(InstanceInfo.class, DistributionInfo.class,
2330                    PackageInfo.class, ConfigurationInfo.class, KeyValueInfo.class);
2331            printXMLOutput(jaxbContext, instance);
2332        } catch (JAXBException e) {
2333            log.error("Output serialization failed: " + e.getMessage());
2334            log.debug(e, e);
2335            errorValue = EXIT_CODE_NOT_RUNNING;
2336        }
2337    }
2338
2339    /**
2340     * @throws PackageException
2341     * @throws IOException
2342     * @throws ConfigurationException
2343     * @since 5.6
2344     */
2345    protected InstanceInfo showConfig() throws IOException, PackageException, ConfigurationException {
2346        InstanceInfo nxInstance = new InstanceInfo();
2347        log.info("***** Nuxeo instance configuration *****");
2348        nxInstance.NUXEO_CONF = configurationGenerator.getNuxeoConf().getPath();
2349        log.info("NUXEO_CONF: " + nxInstance.NUXEO_CONF);
2350        nxInstance.NUXEO_HOME = configurationGenerator.getNuxeoHome().getPath();
2351        log.info("NUXEO_HOME: " + nxInstance.NUXEO_HOME);
2352        // CLID
2353        try {
2354            nxInstance.clid = getConnectBroker().getCLID();
2355            log.info("Instance CLID: " + nxInstance.clid);
2356        } catch (NoCLID e) {
2357            // leave nxInstance.clid unset
2358        } catch (IOException | PackageException e) {
2359            // something went wrong in the NuxeoConnectClient initialization
2360            errorValue = EXIT_CODE_UNAUTHORIZED;
2361            throw new ConfigurationException("Could not initialize NuxeoConnectClient", e);
2362        }
2363        // distribution.properties
2364        DistributionInfo nxDistrib = getDistributionInfo();
2365        nxInstance.distribution = nxDistrib;
2366        log.info("** Distribution");
2367        log.info("- name: " + nxDistrib.name);
2368        log.info("- server: " + nxDistrib.server);
2369        log.info("- version: " + nxDistrib.version);
2370        log.info("- date: " + nxDistrib.date);
2371        log.info("- packaging: " + nxDistrib.packaging);
2372        // packages
2373        List<LocalPackage> pkgs = getConnectBroker().getPkgList();
2374        log.info("** Packages:");
2375        List<String> pkgTemplates = new ArrayList<>();
2376        for (LocalPackage pkg : pkgs) {
2377            nxInstance.packages.add(new PackageInfo(pkg));
2378            log.info(String.format("- %s (version: %s - id: %s - state: %s)", pkg.getName(), pkg.getVersion(),
2379                    pkg.getId(), pkg.getPackageState().getLabel()));
2380            // store template(s) added by this package
2381            try {
2382                File installFile = pkg.getInstallFile();
2383                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
2384                DocumentBuilder db = dbf.newDocumentBuilder();
2385                Document dom = db.parse(installFile);
2386                NodeList nodes = dom.getDocumentElement().getElementsByTagName("config");
2387                for (int i = 0; i < nodes.getLength(); i++) {
2388                    Element node = (Element) nodes.item(i);
2389                    if (node.hasAttribute("addtemplate")) {
2390                        pkgTemplates.add(node.getAttribute("addtemplate"));
2391                    }
2392                }
2393            } catch (Exception e) {
2394                log.warn("Could not parse install file for " + pkg.getName(), e);
2395            }
2396        }
2397        // nuxeo.conf
2398        ConfigurationInfo nxConfig = new ConfigurationInfo();
2399        nxConfig.dbtemplate = configurationGenerator.extractDatabaseTemplateName();
2400        log.info("** Templates:");
2401        log.info("Database template: " + nxConfig.dbtemplate);
2402        String userTemplates = configurationGenerator.getUserTemplates();
2403        StringTokenizer st = new StringTokenizer(userTemplates, ",");
2404        while (st.hasMoreTokens()) {
2405            String template = st.nextToken();
2406            if (template.equals(nxConfig.dbtemplate)) {
2407                continue;
2408            }
2409            if (pkgTemplates.contains(template)) {
2410                nxConfig.pkgtemplates.add(template);
2411                log.info("Package template: " + template);
2412            } else {
2413                File testBase = new File(configurationGenerator.getNuxeoHome(), ConfigurationGenerator.TEMPLATES
2414                        + File.separator + template);
2415                if (testBase.exists()) {
2416                    nxConfig.basetemplates.add(template);
2417                    log.info("Base template: " + template);
2418                } else {
2419                    nxConfig.usertemplates.add(template);
2420                    log.info("User template: " + template);
2421                }
2422            }
2423        }
2424        log.info("** Settings from nuxeo.conf:");
2425        CryptoProperties userConfig = configurationGenerator.getUserConfig();
2426        for (Object item : new TreeSet<>(userConfig.keySet())) {
2427            String key = (String) item;
2428            String value = userConfig.getRawProperty(key);
2429            if (key.equals("JAVA_OPTS")) {
2430                value = getJavaOptsProperty();
2431            }
2432            KeyValueInfo kv = new KeyValueInfo(key, value);
2433            nxConfig.keyvals.add(kv);
2434            if (!key.contains("password") && !key.equals(Environment.SERVER_STATUS_KEY) && !Crypto.isEncrypted(value)) {
2435                log.info(key + "=" + value);
2436            } else {
2437                log.info(key + "=********");
2438            }
2439        }
2440        nxInstance.config = nxConfig;
2441        log.info("****************************************");
2442        if (xmlOutput) {
2443            printInstanceXMLOutput(nxInstance);
2444        }
2445        return nxInstance;
2446    }
2447
2448    /**
2449     * @since 5.9.1
2450     */
2451    protected DistributionInfo getDistributionInfo() {
2452        File distFile = new File(configurationGenerator.getConfigDir(), "distribution.properties");
2453        if (!distFile.exists()) {
2454            // fallback in the file in templates
2455            distFile = new File(configurationGenerator.getNuxeoHome(), "templates");
2456            distFile = new File(distFile, "common");
2457            distFile = new File(distFile, "config");
2458            distFile = new File(distFile, "distribution.properties");
2459        }
2460        DistributionInfo nxDistrib;
2461        try {
2462            nxDistrib = new DistributionInfo(distFile);
2463        } catch (IOException e) {
2464            nxDistrib = new DistributionInfo();
2465        }
2466        return nxDistrib;
2467    }
2468
2469    /**
2470     * @since 5.6
2471     * @param pkgsToAdd
2472     * @param pkgsToInstall
2473     * @param pkgsToUninstall
2474     * @param pkgsToRemove
2475     * @return true if request execution was fine
2476     * @throws IOException
2477     * @throws PackageException
2478     */
2479    protected boolean pkgRequest(List<String> pkgsToAdd, List<String> pkgsToInstall, List<String> pkgsToUninstall,
2480            List<String> pkgsToRemove) throws IOException, PackageException {
2481        boolean cmdOK = true;
2482        if (configurationGenerator.isInstallInProgress()) {
2483            cmdOK = getConnectBroker().executePending(configurationGenerator.getInstallFile(), true, true,
2484                    ignoreMissing);
2485        }
2486        cmdOK = cmdOK
2487                && getConnectBroker().pkgRequest(pkgsToAdd, pkgsToInstall, pkgsToUninstall, pkgsToRemove, true,
2488                        ignoreMissing);
2489        if (!cmdOK) {
2490            errorValue = EXIT_CODE_ERROR;
2491        }
2492        return cmdOK;
2493    }
2494
2495    /**
2496     * Update the cached list of remote packages
2497     *
2498     * @since 5.6
2499     * @return true
2500     * @throws IOException
2501     * @throws PackageException
2502     */
2503    protected boolean pkgRefreshCache() throws IOException, PackageException {
2504        getConnectBroker().refreshCache();
2505        return true;
2506    }
2507
2508    /**
2509     * Add packages from the distribution to the local cache
2510     *
2511     * @throws PackageException
2512     * @throws IOException
2513     * @since 5.6
2514     */
2515    protected boolean pkgInit() throws IOException, PackageException {
2516        return getConnectBroker().addDistributionPackages();
2517    }
2518
2519    /**
2520     * Uninstall and remove all packages from the local cache
2521     *
2522     * @return {@code true} if command succeed
2523     * @throws PackageException
2524     * @throws IOException
2525     * @since 5.6
2526     */
2527    protected boolean pkgPurge() throws PackageException, IOException {
2528        return getConnectBroker().pkgPurge();
2529    }
2530
2531    /**
2532     * Install the hotfixes available for the instance
2533     *
2534     * @return {@code true} if command succeed
2535     * @throws PackageException
2536     * @throws IOException
2537     * @since 5.6
2538     */
2539    protected boolean pkgHotfix() throws IOException, PackageException {
2540        return getConnectBroker().pkgHotfix();
2541    }
2542
2543    /**
2544     * Upgrade the marketplace packages (addons) available for the instance
2545     *
2546     * @return {@code true} if command succeed
2547     * @throws PackageException
2548     * @throws IOException
2549     * @since 5.6
2550     */
2551    protected boolean pkgUpgrade() throws IOException, PackageException {
2552        return getConnectBroker().pkgUpgrade();
2553    }
2554
2555    /**
2556     * Combined install/uninstall request
2557     *
2558     * @param request Space separated list of package names or IDs prefixed with + (install) or - (uninstall)
2559     * @throws IOException
2560     * @throws PackageException
2561     * @since 5.6
2562     */
2563    protected boolean pkgCompoundRequest(List<String> request) throws IOException, PackageException {
2564        List<String> add = new ArrayList<>();
2565        List<String> install = new ArrayList<>();
2566        List<String> uninstall = new ArrayList<>();
2567        for (String param : request) {
2568            for (String subparam : param.split("[ ,]")) {
2569                if (subparam.charAt(0) == '-') {
2570                    uninstall.add(subparam.substring(1));
2571                } else if (subparam.charAt(0) == '+') {
2572                    install.add(subparam.substring(1));
2573                } else {
2574                    add.add(subparam);
2575                }
2576            }
2577        }
2578        return pkgRequest(add, install, uninstall, null);
2579    }
2580
2581    protected boolean pkgSetRequest(List<String> request, boolean nodeps) throws IOException, PackageException {
2582        boolean cmdOK;
2583        if (nodeps) {
2584            cmdOK = getConnectBroker().pkgSet(request, ignoreMissing);
2585        } else {
2586            cmdOK = getConnectBroker().pkgRequest(null, request, null, null, false, ignoreMissing);
2587        }
2588        if (!cmdOK) {
2589            errorValue = EXIT_CODE_ERROR;
2590        }
2591        return cmdOK;
2592    }
2593
2594    /**
2595     * dpkg-like command which returns package location, version, dependencies, conflicts, ...
2596     *
2597     * @param packages List of packages identified by their ID, name or local filename.
2598     * @return false if unable to show package information.
2599     * @throws PackageException
2600     * @throws IOException
2601     * @since 5.7
2602     */
2603    protected boolean pkgShow(String[] packages) throws IOException, PackageException {
2604        boolean cmdOK = getConnectBroker().pkgShow(Arrays.asList(packages));
2605        if (!cmdOK) {
2606            errorValue = EXIT_CODE_ERROR;
2607        }
2608        return cmdOK;
2609    }
2610
2611}