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    /**
585     * @since 5.5
586     * @return true if quiet mode is active
587     */
588    public boolean isQuiet() {
589        return quiet;
590    }
591
592    private static Map<String, NuxeoLauncherGUI> guis;
593
594    /**
595     * @since 5.5
596     */
597    public NuxeoLauncherGUI getGUI() {
598        if (guis == null) {
599            return null;
600        }
601        return guis.get(configurationGenerator.getNuxeoConf().toString());
602    }
603
604    /**
605     * @since 5.5
606     */
607    public void setGUI(NuxeoLauncherGUI gui) {
608        if (guis == null) {
609            guis = new HashMap<>();
610        }
611        guis.put(configurationGenerator.getNuxeoConf().toString(), gui);
612    }
613
614    public NuxeoLauncher(ConfigurationGenerator configurationGenerator) {
615        this.configurationGenerator = configurationGenerator;
616        init();
617    }
618
619    /**
620     * @since 5.6
621     */
622    public void init() {
623        if (!configurationGenerator.init(true)) {
624            throw new IllegalStateException("Initialization failed");
625        }
626        statusServletClient = new StatusServletClient(configurationGenerator);
627        statusServletClient.setKey(configurationGenerator.getUserConfig().getProperty(Environment.SERVER_STATUS_KEY));
628        processManager = getOSProcessManager();
629        processRegex = "^(?!/bin/sh).*" + Pattern.quote(configurationGenerator.getNuxeoConf().getPath()) + ".*"
630                + Pattern.quote(getServerPrint()) + ".*$";
631
632        // Set OS-specific decorations
633        if (PlatformUtils.isMac()) {
634            System.setProperty("com.apple.mrj.application.apple.menu.about.name", "NuxeoCtl");
635        }
636    }
637
638    private ProcessManager getOSProcessManager() {
639        if (PlatformUtils.isLinux() || SystemUtils.IS_OS_AIX) {
640            UnixProcessManager unixProcessManager = new UnixProcessManager();
641            return unixProcessManager;
642        } else if (PlatformUtils.isMac()) {
643            return new MacProcessManager();
644        } else if (SystemUtils.IS_OS_SUN_OS) {
645            return new SolarisProcessManager();
646        } else if (PlatformUtils.isWindows()) {
647            WindowsProcessManager windowsProcessManager = new WindowsProcessManager();
648            return windowsProcessManager.isUsable() ? windowsProcessManager : new PureJavaProcessManager();
649        } else {
650            return new PureJavaProcessManager();
651        }
652    }
653
654    public static class SolarisProcessManager extends UnixProcessManager {
655
656        protected static final String SOLARIS_11 = "5.11";
657
658        protected static final String SOLARIS_10 = "5.10";
659
660        protected static final String[] SOLARIS_11_PS = { "/usr/bin/ps", "auxww" };
661
662        protected static final String[] SOLARIS_10_PS = { "/usr/ucb/ps", "auxww" };
663
664        protected static final Pattern PS_OUTPUT_LINE = Pattern.compile("^" + "[^\\s]+\\s+" // USER
665                + "([0-9]+)\\s+" // PID
666                + "[0-9.\\s]+" // %CPU %MEM SZ RSS (may be collapsed)
667                + "[^\\s]+\\s+" // TT (no starting digit)
668                + "[^\\s]+\\s+" // S
669                + "[^\\s]+\\s+" // START
670                + "[^\\s]+\\s+" // TIME
671                + "(.*)$" // COMMAND
672        );
673
674        protected String solarisVersion;
675
676        protected String getSolarisVersion() {
677            if (solarisVersion == null) {
678                List<String> lines;
679                try {
680                    lines = execute(new String[] { "/usr/bin/uname", "-r" });
681                } catch (IOException e) {
682                    log.debug(e.getMessage(), e);
683                    lines = Collections.emptyList();
684                }
685                if (lines.isEmpty()) {
686                    solarisVersion = "?";
687                } else {
688                    solarisVersion = lines.get(0).trim();
689                }
690            }
691            return solarisVersion;
692        }
693
694        @Override
695        protected String[] psCommand() {
696            if (SOLARIS_11.equals(getSolarisVersion())) {
697                return SOLARIS_11_PS;
698            }
699            return null;
700        }
701
702        protected Matcher getLineMatcher(String line) {
703            return PS_OUTPUT_LINE.matcher(line);
704        }
705
706        @Override
707        public String findPid(String regex) throws IOException {
708            if (SOLARIS_11.equals(getSolarisVersion())) {
709                Pattern commandPattern = Pattern.compile(regex);
710                for (String line : execute(psCommand())) {
711                    Matcher lineMatcher = getLineMatcher(line);
712                    if (lineMatcher.matches()) {
713                        String pid = lineMatcher.group(1);
714                        String command = lineMatcher.group(2);
715                        Matcher commandMatcher = commandPattern.matcher(command);
716                        if (commandMatcher.find()) {
717                            return pid;
718                        }
719                    }
720                }
721            } else {
722                log.debug("Unsupported Solaris version: " + solarisVersion);
723            }
724            return null;
725        }
726
727        protected List<String> execute(String... command) throws IOException {
728            Process process = new ProcessBuilder(command).start();
729            List<String> lines = IOUtils.readLines(process.getInputStream());
730            return lines;
731        }
732    }
733
734    /**
735     * Do not directly call this method without a call to {@link #checkNoRunningServer()}
736     *
737     * @see #doStart()
738     * @throws IOException In case of issue with process.
739     * @throws InterruptedException If any thread has interrupted the current thread.
740     */
741    protected void start(boolean logProcessOutput) throws IOException, InterruptedException {
742        List<String> startCommand = new ArrayList<>();
743        startCommand.add(getJavaExecutable().getPath());
744        startCommand.addAll(Arrays.asList(getJavaOptsProperty().split(" ")));
745        startCommand.add("-cp");
746        startCommand.add(getClassPath());
747        startCommand.addAll(getNuxeoProperties());
748        startCommand.addAll(getServerProperties());
749        setServerStartCommand(startCommand);
750        for (String param : params) {
751            startCommand.add(param);
752        }
753        ProcessBuilder pb = new ProcessBuilder(getOSCommand(startCommand));
754        pb.directory(configurationGenerator.getNuxeoHome());
755        // pb = pb.redirectErrorStream(true);
756        log.debug("Server command: " + pb.command());
757        nuxeoProcess = pb.start();
758        Thread.sleep(1000);
759        boolean processExited = false;
760        // Check if process exited early
761        try {
762            int exitValue = nuxeoProcess.exitValue();
763            if (exitValue != 0) {
764                log.error(String.format("Server start failed (%d).", exitValue));
765            }
766            processExited = true;
767        } catch (IllegalThreadStateException e) {
768            // Normal case
769        }
770        logProcessStreams(nuxeoProcess, processExited || logProcessOutput);
771        if (!processExited) {
772            if (getPid() != null) {
773                log.warn("Server started with process ID " + pid + ".");
774            } else {
775                log.warn("Sent server start command but could not get process ID.");
776            }
777        }
778    }
779
780    /**
781     * Gets the Java options with 'nuxeo.*' properties substituted. It enables usage of property like ${nuxeo.log.dir}
782     * inside JAVA_OPTS.
783     *
784     * @return the java options string.
785     */
786    protected String getJavaOptsProperty() {
787        String ret = System.getProperty(JAVA_OPTS_PROPERTY, JAVA_OPTS_DEFAULT);
788        ret = StrSubstitutor.replace(ret, configurationGenerator.getUserConfig());
789        return ret;
790    }
791
792    /**
793     * Check if some server is already running (from another thread) and throw a Runtime exception if it finds one. That
794     * method will work where {@link #isRunning()} won't.
795     *
796     * @throws IllegalThreadStateException Thrown if a server is already running.
797     */
798    public void checkNoRunningServer() throws IllegalStateException {
799        try {
800            String existingPid = getPid();
801            if (existingPid != null) {
802                errorValue = EXIT_CODE_OK;
803                throw new IllegalStateException("A server is running with process ID " + existingPid);
804            }
805        } catch (IOException e) {
806            log.warn("Could not check existing process: " + e.getMessage());
807        }
808    }
809
810    /**
811     * @return (since 5.5) Array list with created stream gobbler threads.
812     */
813    public ArrayList<ThreadedStreamGobbler> logProcessStreams(Process process, boolean logProcessOutput) {
814        ArrayList<ThreadedStreamGobbler> sgArray = new ArrayList<>();
815        ThreadedStreamGobbler inputSG, errorSG;
816        if (logProcessOutput) {
817            inputSG = new ThreadedStreamGobbler(process.getInputStream(), System.out);
818            errorSG = new ThreadedStreamGobbler(process.getErrorStream(), System.err);
819        } else {
820            inputSG = new ThreadedStreamGobbler(process.getInputStream(), SimpleLog.LOG_LEVEL_OFF);
821            errorSG = new ThreadedStreamGobbler(process.getErrorStream(), SimpleLog.LOG_LEVEL_OFF);
822        }
823        inputSG.start();
824        errorSG.start();
825        sgArray.add(inputSG);
826        sgArray.add(errorSG);
827        return sgArray;
828    }
829
830    protected abstract String getServerPrint();
831
832    /**
833     * Will wrap, if necessary, the command within a Shell command
834     *
835     * @param roughCommand Java command which will be run
836     * @return wrapped command depending on the OS
837     */
838    private List<String> getOSCommand(List<String> roughCommand) {
839        String linearizedCommand = new String();
840        ArrayList<String> osCommand = new ArrayList<>();
841        if (PlatformUtils.isLinux() || PlatformUtils.isMac()) {
842            for (Iterator<String> iterator = roughCommand.iterator(); iterator.hasNext();) {
843                String commandToken = iterator.next();
844                if (commandToken.contains(" ")) {
845                    commandToken = commandToken.replaceAll(" ", "\\\\ ");
846                }
847                linearizedCommand += " " + commandToken;
848            }
849            osCommand.add("/bin/sh");
850            osCommand.add("-c");
851            osCommand.add(linearizedCommand);
852            // osCommand.add("&");
853            return osCommand;
854            // return roughCommand;
855        } else if (PlatformUtils.isWindows()) {
856            // for (Iterator<String> iterator = roughCommand.iterator();
857            // iterator.hasNext();) {
858            // String commandToken = iterator.next();
859            // if (commandToken.endsWith("java")) {
860            // commandToken = "^\"" + commandToken + "^\"";
861            // } else if (commandToken.contains(" ")) {
862            // commandToken = commandToken.replaceAll(" ", "^ ");
863            // }
864            // linearizedCommand += " " + commandToken;
865            // }
866            // osCommand.add("cmd");
867            // osCommand.add("/C");
868            // osCommand.add(linearizedCommand);
869            // return osCommand;
870            return roughCommand;
871        } else {
872            return roughCommand;
873        }
874    }
875
876    protected abstract Collection<? extends String> getServerProperties();
877
878    protected abstract void setServerStartCommand(List<String> command);
879
880    private File getJavaExecutable() {
881        File javaExec = new File(System.getProperty("java.home"), "bin" + File.separator + "java");
882        return javaExec;
883    }
884
885    protected abstract String getClassPath();
886
887    /**
888     * @since 5.6
889     */
890    protected abstract String getShutdownClassPath();
891
892    protected Collection<? extends String> getNuxeoProperties() {
893        ArrayList<String> nuxeoProperties = new ArrayList<>();
894        nuxeoProperties.add(String.format("-D%s=%s", Environment.NUXEO_HOME, configurationGenerator.getNuxeoHome()
895                                                                                                   .getPath()));
896        nuxeoProperties.add(String.format("-D%s=%s", ConfigurationGenerator.NUXEO_CONF,
897                configurationGenerator.getNuxeoConf().getPath()));
898        nuxeoProperties.add(getNuxeoProperty(Environment.NUXEO_LOG_DIR));
899        nuxeoProperties.add(getNuxeoProperty(Environment.NUXEO_DATA_DIR));
900        nuxeoProperties.add(getNuxeoProperty(Environment.NUXEO_TMP_DIR));
901        if (!DEFAULT_NUXEO_CONTEXT_PATH.equals(configurationGenerator.getUserConfig().getProperty(
902                Environment.NUXEO_CONTEXT_PATH))) {
903            nuxeoProperties.add(getNuxeoProperty(Environment.NUXEO_CONTEXT_PATH));
904        }
905        if (overrideJavaTmpDir) {
906            nuxeoProperties.add("-Djava.io.tmpdir="
907                    + configurationGenerator.getUserConfig().getProperty(Environment.NUXEO_TMP_DIR));
908        }
909        return nuxeoProperties;
910    }
911
912    private String getNuxeoProperty(String property) {
913        return "-D" + property + "=" + configurationGenerator.getUserConfig().getProperty(property);
914    }
915
916    protected String addToClassPath(String cp, String filename) {
917        File classPathEntry = new File(configurationGenerator.getNuxeoHome(), filename);
918        if (!classPathEntry.exists()) {
919            classPathEntry = new File(filename);
920        }
921        if (!classPathEntry.exists()) {
922            throw new RuntimeException("Tried to add inexistent classpath entry: " + filename);
923        }
924        cp += System.getProperty("path.separator") + classPathEntry.getPath();
925        return cp;
926    }
927
928    /**
929     * @since 5.6
930     */
931    protected static void initParserOptions() {
932        if (launcherOptions == null) {
933            launcherOptions = new Options();
934            // help option
935            launcherOptions.addOption(Option.builder("h").longOpt(OPTION_HELP).desc(OPTION_HELP_DESC).build());
936            // Quiet option
937            launcherOptions.addOption(Option.builder("q").longOpt(OPTION_QUIET).desc(OPTION_QUIET_DESC).build());
938            { // Debug options (mutually exclusive)
939                OptionGroup debugOptions = new OptionGroup();
940                // Debug option
941                debugOptions.addOption(Option.builder("d")
942                                             .longOpt(OPTION_DEBUG)
943                                             .desc(OPTION_DEBUG_DESC)
944                                             .hasArgs()
945                                             .argName(OPTION_DEBUG_CATEGORY_ARG_NAME)
946                                             .optionalArg(true)
947                                             .valueSeparator(',')
948                                             .build());
949                // Debug category option
950                debugOptions.addOption(Option.builder(OPTION_DEBUG_CATEGORY)
951                                             .desc(OPTION_DEBUG_CATEGORY_DESC)
952                                             .hasArgs()
953                                             .argName(OPTION_DEBUG_CATEGORY_ARG_NAME)
954                                             .optionalArg(true)
955                                             .valueSeparator(',')
956                                             .build());
957                launcherOptions.addOptionGroup(debugOptions);
958            }
959            // For help output purpose only: that option is managed and swallowed by the nuxeoctl Shell script
960            launcherOptions.addOption(Option.builder()
961                                            .longOpt("--debug-launcher")
962                                            .desc("Linux-only. Activate Java debugging mode on the Launcher.")
963                                            .build());
964            // Instance CLID option
965            launcherOptions.addOption(Option.builder().longOpt(OPTION_CLID).desc(OPTION_CLID_DESC).hasArg().build());
966            { // Output options (mutually exclusive)
967                OptionGroup outputOptions = new OptionGroup();
968                // XML option
969                outputOptions.addOption(Option.builder().longOpt(OPTION_XML).desc(OPTION_XML_DESC).build());
970                // JSON option
971                outputOptions.addOption(Option.builder().longOpt(OPTION_JSON).desc(OPTION_JSON_DESC).build());
972                launcherOptions.addOptionGroup(outputOptions);
973            }
974            // GUI option
975            launcherOptions.addOption(Option.builder()
976                                            .longOpt(OPTION_GUI)
977                                            .desc(OPTION_GUI_DESC)
978                                            .hasArg()
979                                            .argName("true|false|yes|no")
980                                            .build());
981            // Package management option
982            launcherOptions.addOption(Option.builder().longOpt(OPTION_NODEPS).desc(OPTION_NODEPS_DESC).build());
983            // Relax on target platform option
984            launcherOptions.addOption(Option.builder()
985                                            .longOpt(OPTION_RELAX)
986                                            .desc(OPTION_RELAX_DESC)
987                                            .hasArg()
988                                            .argName("true|false|yes|no|ask")
989                                            .build());
990            // Accept option
991            launcherOptions.addOption(Option.builder()
992                                            .longOpt(OPTION_ACCEPT)
993                                            .desc(OPTION_ACCEPT_DESC)
994                                            .hasArg()
995                                            .argName("true|false|yes|no|ask")
996                                            .build());
997            // Allow SNAPSHOT option
998            launcherOptions.addOption(Option.builder("s").longOpt(OPTION_SNAPSHOT).desc(OPTION_SNAPSHOT_DESC).build());
999            // Force option
1000            launcherOptions.addOption(Option.builder("f").longOpt(OPTION_FORCE).desc(OPTION_FORCE_DESC).build());
1001            // Strict option
1002            launcherOptions.addOption(Option.builder().longOpt(OPTION_STRICT).desc(OPTION_STRICT_DESC).build());
1003
1004            // Ignore missing option
1005            launcherOptions.addOption(Option.builder("im")
1006                                            .longOpt(OPTION_IGNORE_MISSING)
1007                                            .desc(OPTION_IGNORE_MISSING_DESC)
1008                                            .build());
1009            // Hide deprecation warnings option
1010            launcherOptions.addOption(Option.builder("hdw")
1011                                            .longOpt(OPTION_HIDE_DEPRECATION)
1012                                            .desc(OPTION_HIDE_DEPRECATION_DESC)
1013                                            .build());
1014            // Encrypt option
1015            launcherOptions.addOption(Option.builder()
1016                                            .longOpt(OPTION_ENCRYPT)
1017                                            .desc(OPTION_ENCRYPT_DESC)
1018                                            .hasArg()
1019                                            .argName(OPTION_ENCRYPT_ARG_NAME)
1020                                            .optionalArg(true)
1021                                            .build());
1022            { // Config options (mutually exclusive)
1023                OptionGroup configOptions = new OptionGroup();
1024                // Set option
1025                configOptions.addOption(Option.builder().longOpt(OPTION_SET).desc(OPTION_SET_DESC).build());
1026                configOptions.addOption(Option.builder().longOpt(OPTION_GET).desc(OPTION_GET_DESC).build());
1027                configOptions.addOption(Option.builder()
1028                                              .longOpt(OPTION_GET_REGEXP)
1029                                              .desc(OPTION_GET_REGEXP_DESC)
1030                                              .build());
1031                launcherOptions.addOptionGroup(configOptions);
1032            }
1033        }
1034    }
1035
1036    /**
1037     * @since 5.6
1038     */
1039    protected static CommandLine parseOptions(String[] args) throws ParseException {
1040        initParserOptions();
1041        CommandLineParser parser = new DefaultParser();
1042        CommandLine cmdLine = null;
1043        cmdLine = parser.parse(launcherOptions, args);
1044        if (cmdLine.hasOption(OPTION_HELP)) {
1045            cmdLine.getArgList().add(OPTION_HELP);
1046            setQuiet();
1047        } else if (cmdLine.getArgList().isEmpty()) {
1048            throw new ParseException("Missing command.");
1049        }
1050        // Common options to the Launcher and the ConfigurationGenerator
1051        if (cmdLine.hasOption(OPTION_QUIET) || cmdLine.hasOption(OPTION_XML) || cmdLine.hasOption(OPTION_JSON)) {
1052            setQuiet();
1053        }
1054        if (cmdLine.hasOption(OPTION_DEBUG)) {
1055            setDebug(cmdLine.getOptionValues(OPTION_DEBUG), "org.nuxeo.launcher");
1056        }
1057        if (cmdLine.hasOption(OPTION_DEBUG_CATEGORY)) {
1058            setDebug(cmdLine.getOptionValues(OPTION_DEBUG_CATEGORY), "org.nuxeo.launcher");
1059        }
1060        if (cmdLine.hasOption(OPTION_FORCE) || cmdLine.hasOption(OPTION_STRICT)) {
1061            setStrict(true);
1062        }
1063        return cmdLine;
1064    }
1065
1066    public static void main(String[] args) {
1067        NuxeoLauncher launcher = null;
1068        try {
1069            launcher = createLauncher(args);
1070            if (launcher.commandRequiresNoGUI()) {
1071                launcher.useGui = false;
1072            }
1073            if (launcher.useGui && launcher.getGUI() == null) {
1074                launcher.setGUI(new NuxeoLauncherGUI(launcher));
1075            }
1076            launch(launcher);
1077        } catch (ParseException e) {
1078            log.error("Invalid command line. " + e.getMessage());
1079            log.debug(e, e);
1080            printShortHelp();
1081            System.exit(launcher == null || launcher.errorValue == EXIT_CODE_OK ? EXIT_CODE_INVALID
1082                    : launcher.errorValue);
1083        } catch (IOException | PackageException | ConfigurationException | GeneralSecurityException e) {
1084            log.error(e.getMessage());
1085            log.debug(e, e);
1086            System.exit(launcher == null || launcher.errorValue == EXIT_CODE_OK ? EXIT_CODE_INVALID
1087                    : launcher.errorValue);
1088        } catch (Exception e) {
1089            log.error("Cannot execute command. " + e.getMessage());
1090            log.debug(e, e);
1091            System.exit(1);
1092        }
1093    }
1094
1095    /**
1096     * @since 5.5
1097     * @param launcher
1098     * @throws PackageException
1099     * @throws IOException
1100     * @throws ConfigurationException
1101     * @throws ParseException
1102     * @throws GeneralSecurityException
1103     */
1104    public static void launch(final NuxeoLauncher launcher) throws IOException, PackageException,
1105            ConfigurationException, ParseException, GeneralSecurityException {
1106        boolean commandSucceeded = true;
1107        if (launcher.commandIs(null)) {
1108            return;
1109        }
1110        if (launcher.commandRequiresNoRunningServer()) {
1111            launcher.checkNoRunningServer();
1112        }
1113        if (launcher.commandIs(OPTION_HELP)) {
1114            printLongHelp();
1115        } else if (launcher.commandIs("status")) {
1116            String statusMsg = launcher.status();
1117            launcher.errorValue = launcher.getStatus();
1118            if (!quiet) {
1119                log.warn(statusMsg);
1120                if (launcher.isStarted()) {
1121                    log.info("Go to " + launcher.getURL());
1122                    log.info(launcher.getStartupSummary());
1123                }
1124            }
1125        } else if (launcher.commandIs("startbg")) {
1126            commandSucceeded = launcher.doStart();
1127        } else if (launcher.commandIs("start")) {
1128            if (launcher.useGui) {
1129                launcher.getGUI().start();
1130            } else {
1131                commandSucceeded = launcher.doStartAndWait();
1132            }
1133        } else if (launcher.commandIs("console")) {
1134            launcher.executor.execute(new Runnable() {
1135                @Override
1136                public void run() {
1137                    launcher.addShutdownHook();
1138                    try {
1139                        if (!launcher.doStart(true)) {
1140                            launcher.removeShutdownHook();
1141                            System.exit(1);
1142                        } else if (!quiet) {
1143                            log.info("Go to " + launcher.getURL());
1144                        }
1145                    } catch (PackageException e) {
1146                        log.error("Could not initialize the packaging subsystem", e);
1147                        launcher.removeShutdownHook();
1148                        System.exit(EXIT_CODE_ERROR);
1149                    }
1150                }
1151            });
1152        } else if (launcher.commandIs("stop")) {
1153            if (launcher.useGui) {
1154                launcher.getGUI().stop();
1155            } else {
1156                launcher.stop();
1157            }
1158        } else if (launcher.commandIs("restartbg")) {
1159            launcher.stop();
1160            commandSucceeded = launcher.doStart();
1161        } else if (launcher.commandIs("restart")) {
1162            launcher.stop();
1163            commandSucceeded = launcher.doStartAndWait();
1164        } else if (launcher.commandIs("wizard")) {
1165            commandSucceeded = launcher.startWizard();
1166        } else if (launcher.commandIs("configure")) {
1167            launcher.configure();
1168        } else if (launcher.commandIs("pack")) {
1169            launcher.pack();
1170        } else if (launcher.commandIs("mp-list")) {
1171            launcher.pkgList();
1172        } else if (launcher.commandIs("mp-listall")) {
1173            launcher.pkgListAll();
1174        } else if (launcher.commandIs("mp-init")) {
1175            commandSucceeded = launcher.pkgInit();
1176        } else if (launcher.commandIs("mp-purge")) {
1177            commandSucceeded = launcher.pkgPurge();
1178        } else if (launcher.commandIs("mp-add")) {
1179            if (launcher.cmdLine.hasOption(OPTION_NODEPS)) {
1180                commandSucceeded = launcher.pkgAdd(launcher.params);
1181            } else {
1182                commandSucceeded = launcher.pkgRequest(Arrays.asList(launcher.params), null, null, null);
1183            }
1184        } else if (launcher.commandIs("mp-install")) {
1185            if (launcher.cmdLine.hasOption(OPTION_NODEPS)) {
1186                commandSucceeded = launcher.pkgInstall(launcher.params);
1187            } else {
1188                commandSucceeded = launcher.pkgRequest(null, Arrays.asList(launcher.params), null, null);
1189            }
1190        } else if (launcher.commandIs("mp-uninstall")) {
1191            if (launcher.cmdLine.hasOption(OPTION_NODEPS)) {
1192                commandSucceeded = launcher.pkgUninstall(launcher.params);
1193            } else {
1194                commandSucceeded = launcher.pkgRequest(null, null, Arrays.asList(launcher.params), null);
1195            }
1196        } else if (launcher.commandIs("mp-remove")) {
1197            if (launcher.cmdLine.hasOption(OPTION_NODEPS)) {
1198                commandSucceeded = launcher.pkgRemove(launcher.params);
1199            } else {
1200                commandSucceeded = launcher.pkgRequest(null, null, null, Arrays.asList(launcher.params));
1201            }
1202        } else if (launcher.commandIs("mp-request")) {
1203            if (launcher.cmdLine.hasOption(OPTION_NODEPS)) {
1204                throw new ParseException("The command mp-request is not available with the --nodeps option");
1205            } else {
1206                commandSucceeded = launcher.pkgCompoundRequest(Arrays.asList(launcher.params));
1207            }
1208        } else if (launcher.commandIs("mp-set")) {
1209            commandSucceeded = launcher.pkgSetRequest(Arrays.asList(launcher.params),
1210                    launcher.cmdLine.hasOption(OPTION_NODEPS));
1211        } else if (launcher.commandIs("mp-hotfix")) {
1212            commandSucceeded = launcher.pkgHotfix();
1213        } else if (launcher.commandIs("mp-upgrade")) {
1214            commandSucceeded = launcher.pkgUpgrade();
1215        } else if (launcher.commandIs("mp-reset")) {
1216            commandSucceeded = launcher.pkgReset();
1217        } else if (launcher.commandIs("mp-update")) {
1218            commandSucceeded = launcher.pkgRefreshCache();
1219        } else if (launcher.commandIs("showconf")) {
1220            launcher.showConfig();
1221        } else if (launcher.commandIs("mp-show")) {
1222            commandSucceeded = launcher.pkgShow(launcher.params);
1223        } else if (launcher.commandIs("encrypt")) {
1224            launcher.encrypt();
1225        } else if (launcher.commandIs("decrypt")) {
1226            launcher.decrypt();
1227        } else if (launcher.commandIs("config")) {
1228            launcher.config();
1229        } else {
1230            log.error("Unknown command " + launcher.command);
1231            printLongHelp();
1232            launcher.errorValue = EXIT_CODE_INVALID;
1233        }
1234        if (launcher.xmlOutput && launcher.command.startsWith("mp-")) {
1235            launcher.printXMLOutput();
1236        }
1237        commandSucceeded = commandSucceeded && launcher.errorValue == EXIT_CODE_OK;
1238        if (!commandSucceeded && !quiet || debug) {
1239            launcher.cset.log(commandSucceeded && debug);
1240        }
1241        if (!commandSucceeded) {
1242            System.exit(launcher.errorValue);
1243        }
1244    }
1245
1246    /**
1247     * @throws ConfigurationException
1248     * @throws GeneralSecurityException
1249     * @since 7.4
1250     */
1251    protected void encrypt() throws ConfigurationException, GeneralSecurityException {
1252        Crypto crypto = configurationGenerator.getCrypto();
1253        String algorithm = cmdLine.getOptionValue(OPTION_ENCRYPT, null);
1254        if (params.length == 0) {
1255            Console console = System.console();
1256            if (console == null) {
1257                errorValue = EXIT_CODE_INVALID;
1258                return;
1259            }
1260            params = new String[] { console.readLine("Please enter the value to encrypt: ") };
1261        }
1262        for (String strToEncrypt : params) {
1263            String encryptedString = crypto.encrypt(algorithm, strToEncrypt.getBytes());
1264            System.out.println(encryptedString);
1265        }
1266    }
1267
1268    /**
1269     * @throws ConfigurationException
1270     * @since 7.4
1271     */
1272    protected void decrypt() throws ConfigurationException {
1273        Crypto crypto = configurationGenerator.getCrypto();
1274        Console console = System.console();
1275        if (console == null) {
1276            errorValue = EXIT_CODE_ERROR;
1277            return;
1278        }
1279        if (!crypto.verifyKey(console.readPassword("Please enter the secret key: "))) {
1280            errorValue = EXIT_CODE_INVALID;
1281            return;
1282        }
1283        for (String strToDecrypt : params) {
1284            System.out.println(Crypto.getChars(crypto.decrypt(strToDecrypt)));
1285        }
1286    }
1287
1288    /**
1289     * @throws ConfigurationException
1290     * @throws IOException
1291     * @throws GeneralSecurityException
1292     * @since 7.4
1293     */
1294    protected void config() throws ConfigurationException, IOException, GeneralSecurityException {
1295        if (cmdLine.hasOption(OPTION_SET) || !cmdLine.hasOption(OPTION_GET) && !cmdLine.hasOption(OPTION_GET_REGEXP)
1296                && params.length == 2) {
1297            setConfigProperties();
1298        } else { // OPTION_GET || OPTION_GET_REGEXP || !OPTION_SET && params.length != 2
1299            getConfigProperties();
1300        }
1301    }
1302
1303    /**
1304     * @since 7.4
1305     */
1306    protected void getConfigProperties() {
1307        boolean isRegexp = cmdLine.hasOption(OPTION_GET_REGEXP);
1308        CryptoProperties userConfig = configurationGenerator.getUserConfig();
1309        List<String> keys;
1310        if (isRegexp) {
1311            keys = new ArrayList<>();
1312            for (Object key : userConfig.keySet()) {
1313                for (String param : params) {
1314                    Pattern pattern = Pattern.compile(param, Pattern.CASE_INSENSITIVE);
1315                    if (pattern.matcher((String) key).find()) {
1316                        keys.add((String) key);
1317                    }
1318                }
1319            }
1320            if (keys.isEmpty()) {
1321                errorValue = EXIT_CODE_NOT_CONFIGURED;
1322            }
1323        } else {
1324            keys = Arrays.asList(params);
1325        }
1326
1327        Crypto crypto = userConfig.getCrypto();
1328        boolean keyChecked = false; // Secret key is asked only once
1329        boolean raw = true;
1330        StringBuilder sb = new StringBuilder();
1331        final String newLine = System.getProperty("line.separator");
1332        for (String key : keys) {
1333            String value = userConfig.getProperty(key, raw);
1334            if (value == null) {
1335                errorValue = EXIT_CODE_NOT_CONFIGURED;
1336                sb.append(OUTPUT_UNSET_VALUE + newLine);
1337            } else {
1338                if (raw && !keyChecked && Crypto.isEncrypted(value)) {
1339                    keyChecked = true;
1340                    Console console = System.console();
1341                    if (console != null && crypto.verifyKey(console.readPassword("Please enter the secret key: "))) {
1342                        raw = false;
1343                        value = new String(crypto.decrypt(value));
1344                    } else {
1345                        errorValue = EXIT_CODE_ERROR;
1346                    }
1347                }
1348                if (isRegexp) {
1349                    sb.append(key + "=");
1350                }
1351                sb.append(value + newLine);
1352            }
1353        }
1354        System.out.println(sb.toString());
1355    }
1356
1357    /**
1358     * @throws IOException
1359     * @throws GeneralSecurityException
1360     * @since 7.4
1361     */
1362    protected void setConfigProperties() throws ConfigurationException, IOException, GeneralSecurityException {
1363        Crypto crypto = configurationGenerator.getCrypto();
1364        boolean doEncrypt = cmdLine.hasOption(OPTION_ENCRYPT);
1365        String algorithm = cmdLine.getOptionValue(OPTION_ENCRYPT, null);
1366        Map<String, String> changedParameters = new HashMap<>();
1367        for (Iterator<String> iterator = Arrays.asList(params).iterator(); iterator.hasNext();) {
1368            String key = iterator.next();
1369            String value;
1370            if (iterator.hasNext()) {
1371                value = iterator.next();
1372                if (doEncrypt) {
1373                    value = crypto.encrypt(algorithm, value.getBytes());
1374                } else if (Environment.CRYPT_KEY.equals(key) || Environment.CRYPT_KEYSTORE_PASS.equals(key)) {
1375                    value = Base64.encodeBase64String(value.getBytes());
1376                }
1377            } else {
1378                Console console = System.console();
1379                if (console == null) {
1380                    errorValue = EXIT_CODE_INVALID;
1381                    return;
1382                }
1383                if (doEncrypt) {
1384                    value = crypto.encrypt(algorithm,
1385                            Crypto.getBytes(console.readPassword("Please enter the value for %s: ", key)));
1386                } else if (Environment.CRYPT_KEY.equals(key) || Environment.CRYPT_KEYSTORE_PASS.equals(key)) {
1387                    value = Base64.encodeBase64String(Crypto.getBytes(console.readPassword(
1388                            "Please enter the value for %s: ", key)));
1389                } else {
1390                    value = console.readLine("Please enter the value for %s: ", key);
1391                }
1392            }
1393            changedParameters.put(key, value);
1394        }
1395        String template = cmdLine.getOptionValue(OPTION_SET);
1396        Map<String, String> oldValues;
1397        if (template == null) {
1398            oldValues = configurationGenerator.setProperties(changedParameters);
1399        } else {
1400            oldValues = configurationGenerator.setProperties(template, changedParameters);
1401        }
1402        log.debug("Old values: " + oldValues);
1403    }
1404
1405    /**
1406     * Since 5.5
1407     */
1408    protected boolean pack() {
1409        try {
1410            configurationGenerator.setProperty(PARAM_UPDATECENTER_DISABLED, "true");
1411            List<String> startCommand = new ArrayList<>();
1412            startCommand.add(getJavaExecutable().getPath());
1413            startCommand.addAll(Arrays.asList(getJavaOptsProperty().split(" ")));
1414            startCommand.add("-cp");
1415            String classpath = getClassPath();
1416            classpath = addToClassPath(classpath, "bin" + File.separator + "nuxeo-launcher.jar");
1417            classpath = getClassPath(classpath, configurationGenerator.getServerConfigurator().getServerLibDir());
1418            classpath = getClassPath(classpath, configurationGenerator.getServerConfigurator().getNuxeoLibDir());
1419            classpath = getClassPath(classpath, new File(configurationGenerator.getRuntimeHome(), "bundles"));
1420            startCommand.add(classpath);
1421            startCommand.addAll(getNuxeoProperties());
1422            if (configurationGenerator.isTomcat) {
1423                startCommand.add(PACK_TOMCAT_CLASS);
1424            } else {
1425                errorValue = EXIT_CODE_ERROR;
1426                return false;
1427            }
1428            startCommand.add(configurationGenerator.getRuntimeHome().getPath());
1429            for (String param : params) {
1430                startCommand.add(param);
1431            }
1432            ProcessBuilder pb = new ProcessBuilder(getOSCommand(startCommand));
1433            pb.directory(configurationGenerator.getNuxeoHome());
1434            log.debug("Pack command: " + pb.command());
1435            Process process = pb.start();
1436            ArrayList<ThreadedStreamGobbler> sgArray = logProcessStreams(process, true);
1437            Thread.sleep(100);
1438            process.waitFor();
1439            waitForProcessStreams(sgArray);
1440        } catch (IOException | InterruptedException e) {
1441            errorValue = EXIT_CODE_ERROR;
1442            log.error("Could not start process", e);
1443        } catch (ConfigurationException e) {
1444            errorValue = EXIT_CODE_ERROR;
1445            log.error(e);
1446        }
1447        return errorValue == EXIT_CODE_OK;
1448    }
1449
1450    protected boolean startWizard() throws PackageException {
1451        if (!configurationGenerator.getServerConfigurator().isWizardAvailable()) {
1452            log.error("Sorry, the wizard is not available within that server.");
1453            return false;
1454        }
1455        if (isRunning()) {
1456            log.error("Server already running. " + "Please stop it before calling \"wizard\" command "
1457                    + "or use the Admin Center instead of the wizard.");
1458            return false;
1459        }
1460        if (reloadConfiguration) {
1461            configurationGenerator = new ConfigurationGenerator(quiet, debug);
1462            configurationGenerator.init();
1463            reloadConfiguration = false;
1464        }
1465        configurationGenerator.getUserConfig().setProperty(ConfigurationGenerator.PARAM_WIZARD_DONE, "false");
1466        return doStart();
1467    }
1468
1469    /**
1470     * @throws PackageException
1471     * @see #doStartAndWait(boolean)
1472     */
1473    public boolean doStartAndWait() throws PackageException {
1474        boolean started = doStartAndWait(false);
1475        if (started && !quiet) {
1476            log.info("Go to " + getURL());
1477        }
1478        return started;
1479    }
1480
1481    /**
1482     * @see #stop(boolean)
1483     */
1484    public void stop() {
1485        stop(false);
1486    }
1487
1488    /**
1489     * Call {@link #doStart(boolean)} with false as parameter.
1490     *
1491     * @see #doStart(boolean)
1492     * @return true if the server started successfully
1493     * @throws PackageException
1494     */
1495    public boolean doStart() throws PackageException {
1496        boolean started = doStart(false);
1497        if (started && !quiet) {
1498            log.info("Go to " + getURL());
1499        }
1500        return started;
1501    }
1502
1503    /**
1504     * Whereas {@link #doStart()} considers the server as started when the process is running, {@link #doStartAndWait()}
1505     * waits for effective start by watching the logs
1506     *
1507     * @param logProcessOutput Must process output stream must be logged or not.
1508     * @return true if the server started successfully
1509     * @throws PackageException
1510     */
1511    public boolean doStartAndWait(boolean logProcessOutput) throws PackageException {
1512        boolean commandSucceeded = false;
1513        if (doStart(logProcessOutput)) {
1514            addShutdownHook();
1515            try {
1516                if (configurationGenerator.isWizardRequired() || waitForEffectiveStart()) {
1517                    commandSucceeded = true;
1518                }
1519                removeShutdownHook();
1520            } catch (InterruptedException e) {
1521                // do nothing
1522            }
1523        }
1524        return commandSucceeded;
1525    }
1526
1527    protected void removeShutdownHook() {
1528        try {
1529            Runtime.getRuntime().removeShutdownHook(shutdownHook);
1530            log.debug("Removed shutdown hook");
1531        } catch (IllegalStateException e) {
1532            // the virtual machine is already in the process of shutting down
1533        }
1534    }
1535
1536    /**
1537     * @return true if Nuxeo is ready
1538     * @throws InterruptedException
1539     */
1540    protected boolean waitForEffectiveStart() throws InterruptedException {
1541        long startTime = new Date().getTime();
1542        int startMaxWait = Integer.parseInt(configurationGenerator.getUserConfig().getProperty(START_MAX_WAIT_PARAM,
1543                getDefaultMaxWait()));
1544        log.debug("Will wait for effective start during " + startMaxWait + " seconds.");
1545        final StringBuilder startSummary = new StringBuilder();
1546        final String newLine = System.getProperty("line.separator");
1547        boolean isReady = false;
1548        long deltaTime = 0;
1549        // Wait for status servlet ready
1550        do {
1551            try {
1552                isReady = statusServletClient.init();
1553            } catch (SocketTimeoutException e) {
1554                if (!quiet) {
1555                    System.out.print(".");
1556                }
1557            }
1558            deltaTime = (new Date().getTime() - startTime) / 1000;
1559        } while (!isReady && deltaTime < startMaxWait && isRunning());
1560        isReady = false;
1561        // Wait for effective start reported from status servlet
1562        do {
1563            isReady = isStarted();
1564            if (!isReady) {
1565                if (!quiet) {
1566                    System.out.print(".");
1567                }
1568                Thread.sleep(1000);
1569            }
1570            deltaTime = (new Date().getTime() - startTime) / 1000;
1571        } while (!isReady && deltaTime < startMaxWait && isRunning());
1572        if (isReady) {
1573            startSummary.append(newLine + getStartupSummary());
1574            long duration = (new Date().getTime() - startTime) / 1000;
1575            startSummary.append("Started in "
1576                    + String.format("%dmin%02ds", new Long(duration / 60), new Long(duration % 60)));
1577            if (wasStartupFine()) {
1578                if (!quiet) {
1579                    System.out.println(startSummary);
1580                }
1581            } else {
1582                System.err.println(startSummary);
1583                if (strict) {
1584                    errorValue = EXIT_CODE_ERROR;
1585                    log.error("Shutting down because of unstarted component in strict mode...");
1586                    stop();
1587                    return false;
1588                }
1589            }
1590            return true;
1591        } else if (deltaTime >= startMaxWait) {
1592            if (!quiet) {
1593                System.out.println();
1594            }
1595            log.error("Starting process is taking too long - giving up.");
1596        }
1597        errorValue = EXIT_CODE_ERROR;
1598        return false;
1599    }
1600
1601    /**
1602     * Must be called after {@link #getStartupSummary()}
1603     *
1604     * @since 5.5
1605     * @return last detected status of running Nuxeo server
1606     */
1607    public boolean wasStartupFine() {
1608        return statusServletClient.isStartupFine();
1609    }
1610
1611    /**
1612     * @since 5.5
1613     * @return Nuxeo startup summary
1614     */
1615    public String getStartupSummary() {
1616        try {
1617            return statusServletClient.getStartupSummary();
1618        } catch (SocketTimeoutException e) {
1619            log.warn("Failed to contact Nuxeo for getting startup summary", e);
1620            return "";
1621        }
1622    }
1623
1624    /**
1625     * Starts the server in background.
1626     *
1627     * @return true if server successfully started
1628     * @throws PackageException
1629     */
1630    public boolean doStart(boolean logProcessOutput) throws PackageException {
1631        errorValue = EXIT_CODE_OK;
1632        boolean serverStarted = false;
1633        try {
1634            if (reloadConfiguration) {
1635                configurationGenerator = new ConfigurationGenerator(quiet, debug);
1636                configurationGenerator.init();
1637            } else {
1638                // Ensure reload on next start
1639                reloadConfiguration = true;
1640            }
1641            configure();
1642            configurationGenerator.verifyInstallation();
1643
1644            if (configurationGenerator.isWizardRequired()) {
1645                if (!configurationGenerator.isForceGeneration()) {
1646                    log.error("Cannot start setup wizard with " + ConfigurationGenerator.PARAM_FORCE_GENERATION
1647                            + "=false. Either set it to true or once, either set "
1648                            + ConfigurationGenerator.PARAM_WIZARD_DONE + "=true to skip the wizard.");
1649                    errorValue = EXIT_CODE_NOT_CONFIGURED;
1650                    return false;
1651                }
1652                String paramsStr = "";
1653                for (String param : params) {
1654                    paramsStr += " " + param;
1655                }
1656                System.setProperty(ConfigurationGenerator.PARAM_WIZARD_RESTART_PARAMS, paramsStr);
1657                configurationGenerator.prepareWizardStart();
1658            } else {
1659                configurationGenerator.cleanupPostWizard();
1660            }
1661
1662            log.debug("Check if install in progress...");
1663            if (configurationGenerator.isInstallInProgress()) {
1664                getConnectBroker().executePending(configurationGenerator.getInstallFile(), true, true);
1665                // configuration will be reloaded, keep wizard value
1666                System.setProperty(
1667                        ConfigurationGenerator.PARAM_WIZARD_DONE,
1668                        configurationGenerator.getUserConfig().getProperty(ConfigurationGenerator.PARAM_WIZARD_DONE,
1669                                "true"));
1670                return doStart(logProcessOutput);
1671            }
1672
1673            start(logProcessOutput);
1674            serverStarted = isRunning();
1675            if (pid != null) {
1676                File pidFile = new File(configurationGenerator.getPidDir(), "nuxeo.pid");
1677                FileWriter writer = new FileWriter(pidFile);
1678                writer.write(pid);
1679                writer.close();
1680            }
1681        } catch (ConfigurationException e) {
1682            errorValue = EXIT_CODE_NOT_CONFIGURED;
1683            log.error("Could not run configuration: " + e.getMessage());
1684            log.debug(e, e);
1685        } catch (IOException e) {
1686            errorValue = EXIT_CODE_ERROR;
1687            log.error("Could not start process: " + e.getMessage());
1688            log.debug(e, e);
1689        } catch (InterruptedException e) {
1690            errorValue = EXIT_CODE_ERROR;
1691            log.error("Could not start process: " + e.getMessage());
1692            log.debug(e, e);
1693        } catch (IllegalStateException e) {
1694            if (strict) {
1695                // assume program is not configured because of http port binding
1696                // conflict
1697                errorValue = EXIT_CODE_NOT_CONFIGURED;
1698            }
1699            log.error(e.getMessage());
1700        }
1701        return serverStarted;
1702    }
1703
1704    /**
1705     * @since 5.6
1706     */
1707    protected void printXMLOutput() {
1708        try {
1709            JAXBContext jaxbContext = JAXBContext.newInstance(CommandSetInfo.class, CommandInfo.class,
1710                    PackageInfo.class, MessageInfo.class);
1711            printXMLOutput(jaxbContext, cset);
1712        } catch (JAXBException e) {
1713            log.error("Output serialization failed: " + e.getMessage(), e);
1714            errorValue = EXIT_CODE_NOT_RUNNING;
1715        }
1716    }
1717
1718    /**
1719     * @since 5.6
1720     */
1721    protected void printXMLOutput(JAXBContext jaxbContext, Object objectToOutput) {
1722        try {
1723            Writer xml = new StringWriter();
1724            Marshaller marshaller = jaxbContext.createMarshaller();
1725            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
1726            marshaller.marshal(objectToOutput, xml);
1727            if (!jsonOutput) {
1728                System.out.println(xml.toString());
1729            } else {
1730                try {
1731                    System.out.println(XML.toJSONObject(xml.toString()).toString(2));
1732                } catch (JSONException e) {
1733                    log.error(String.format("XML to JSON conversion failed: %s\nOutput was:\n%s", e.getMessage(),
1734                            xml.toString()));
1735                }
1736            }
1737        } catch (JAXBException e) {
1738            log.error("Output serialization failed: " + e.getMessage(), e);
1739            errorValue = EXIT_CODE_NOT_RUNNING;
1740        }
1741    }
1742
1743    /**
1744     * Stop stream gobblers contained in the given ArrayList
1745     *
1746     * @since 5.5
1747     * @see #logProcessStreams(Process, boolean)
1748     */
1749    public void waitForProcessStreams(ArrayList<ThreadedStreamGobbler> sgArray) {
1750        for (ThreadedStreamGobbler streamGobbler : sgArray) {
1751            try {
1752                streamGobbler.join(STREAM_MAX_WAIT);
1753            } catch (InterruptedException e) {
1754                streamGobbler.interrupt();
1755            }
1756        }
1757    }
1758
1759    /**
1760     * @since 5.5
1761     * @param classpath
1762     * @param baseDir
1763     * @return classpath with all jar files in baseDir
1764     * @throws IOException
1765     */
1766    protected String getClassPath(String classpath, File baseDir) throws IOException {
1767        File[] files = getFilename(baseDir, ".*");
1768        for (File file : files) {
1769            classpath += System.getProperty("path.separator") + file.getPath();
1770        }
1771        return classpath;
1772    }
1773
1774    /**
1775     * @since 5.5
1776     * @param baseDir
1777     * @param filePattern
1778     * @return filename matching filePattern in baseDir
1779     */
1780    protected File[] getFilename(File baseDir, final String filePattern) {
1781        File[] files = baseDir.listFiles(new FilenameFilter() {
1782            @Override
1783            public boolean accept(File basedir, String filename) {
1784                return filename.matches(filePattern + "(-[0-9].*)?\\.jar");
1785            }
1786        });
1787        return files;
1788    }
1789
1790    protected class ShutdownThread extends Thread {
1791
1792        private NuxeoLauncher launcher;
1793
1794        public ShutdownThread(NuxeoLauncher launcher) {
1795            super();
1796            this.launcher = launcher;
1797        }
1798
1799        @Override
1800        public void run() {
1801            log.debug("Shutting down...");
1802            if (launcher.isRunning()) {
1803                launcher.stop();
1804            }
1805            log.debug("Shutdown complete.");
1806        }
1807    }
1808
1809    protected void addShutdownHook() {
1810        log.debug("Add shutdown hook");
1811        shutdownHook = new ShutdownThread(this);
1812        Runtime.getRuntime().addShutdownHook(shutdownHook);
1813    }
1814
1815    /**
1816     * Stops the server. Will try to call specific class for a clean stop, retry, waiting between each try, then kill
1817     * the process if still running.
1818     */
1819    public void stop(boolean logProcessOutput) {
1820        long startTime = new Date().getTime();
1821        long deltaTime;
1822        try {
1823            if (!isRunning()) {
1824                log.warn("Server is not running.");
1825                return;
1826            }
1827            if (!quiet) {
1828                System.out.print("\nStopping server...");
1829            }
1830            int nbTry = 0;
1831            boolean retry = false;
1832            int stopMaxWait = Integer.parseInt(configurationGenerator.getUserConfig().getProperty(STOP_MAX_WAIT_PARAM,
1833                    STOP_MAX_WAIT_DEFAULT));
1834            do {
1835                List<String> stopCommand = new ArrayList<>();
1836                stopCommand.add(getJavaExecutable().getPath());
1837                stopCommand.add("-cp");
1838                stopCommand.add(getShutdownClassPath());
1839                stopCommand.addAll(getNuxeoProperties());
1840                stopCommand.addAll(getServerProperties());
1841                setServerStopCommand(stopCommand);
1842                for (String param : params) {
1843                    stopCommand.add(param);
1844                }
1845                ProcessBuilder pb = new ProcessBuilder(getOSCommand(stopCommand));
1846                pb.directory(configurationGenerator.getNuxeoHome());
1847                // pb = pb.redirectErrorStream(true);
1848                log.debug("Server command: " + pb.command());
1849                try {
1850                    Process stopProcess = pb.start();
1851                    ArrayList<ThreadedStreamGobbler> sgArray = logProcessStreams(stopProcess, logProcessOutput);
1852                    stopProcess.waitFor();
1853                    waitForProcessStreams(sgArray);
1854                    boolean wait = true;
1855                    while (wait) {
1856                        try {
1857                            if (stopProcess.exitValue() == 0) {
1858                                // Successful call for server stop
1859                                retry = false;
1860                            } else {
1861                                // Failed to call for server stop
1862                                retry = ++nbTry < STOP_NB_TRY;
1863                                if (!quiet) {
1864                                    System.out.print(".");
1865                                }
1866                                Thread.sleep(STOP_SECONDS_BEFORE_NEXT_TRY * 1000);
1867                            }
1868                            wait = false;
1869                        } catch (IllegalThreadStateException e) {
1870                            // Stop call is still running
1871                            wait = true;
1872                            if (!quiet) {
1873                                System.out.print(".");
1874                            }
1875                            Thread.sleep(1000);
1876                        }
1877                    }
1878                    // Exit if there's no way to check for server stop
1879                    if (!processManager.canFindPid()) {
1880                        log.warn("Can't check server status on your OS.");
1881                        return;
1882                    }
1883                    // Wait a few seconds for effective stop
1884                    deltaTime = 0;
1885                    do {
1886                        if (!quiet) {
1887                            System.out.print(".");
1888                        }
1889                        Thread.sleep(1000);
1890                        deltaTime = (new Date().getTime() - startTime) / 1000;
1891                    } while (!retry && getPid() != null && deltaTime < stopMaxWait);
1892                } catch (InterruptedException e) {
1893                    log.error(e);
1894                }
1895            } while (retry);
1896            if (getPid() == null) {
1897                log.warn("Server stopped.");
1898            } else {
1899                log.info("No answer from server, try to kill process " + pid + "...");
1900                processManager.kill(nuxeoProcess, pid);
1901                if (getPid() == null) {
1902                    log.warn("Server forcibly stopped.");
1903                }
1904            }
1905        } catch (IOException e) {
1906            log.error("Could not manage process!", e);
1907        }
1908    }
1909
1910    protected abstract void setServerStopCommand(List<String> command);
1911
1912    private String getPid() throws IOException {
1913        pid = processManager.findPid(processRegex);
1914        log.debug("regexp: " + processRegex + " pid:" + pid);
1915        return pid;
1916    }
1917
1918    /**
1919     * Configure the server after checking installation
1920     *
1921     * @throws ConfigurationException If an installation error is detected or if configuration fails
1922     */
1923    public void configure() throws ConfigurationException {
1924        try {
1925            checkNoRunningServer();
1926            configurationGenerator.checkJavaVersion();
1927            configurationGenerator.run();
1928            overrideJavaTmpDir = Boolean.parseBoolean(configurationGenerator.getUserConfig().getProperty(
1929                    OVERRIDE_JAVA_TMPDIR_PARAM, "true"));
1930        } catch (ConfigurationException e) {
1931            errorValue = EXIT_CODE_NOT_CONFIGURED;
1932            throw e;
1933        }
1934    }
1935
1936    /**
1937     * @return Default max wait depending on server (ie JBoss takes much more time than Tomcat)
1938     */
1939    private String getDefaultMaxWait() {
1940        return START_MAX_WAIT_DEFAULT;
1941    }
1942
1943    /**
1944     * Return process status (running or not) as String, depending on OS capability to manage processes. Set status
1945     * value following "http://refspecs.freestandards.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html"
1946     *
1947     * @see #getStatus()
1948     */
1949    public String status() {
1950        try {
1951            if (processManager instanceof PureJavaProcessManager) {
1952                status = STATUS_CODE_UNKNOWN;
1953                return "Can't check server status on your OS.";
1954            }
1955            if (getPid() == null) {
1956                status = STATUS_CODE_OFF;
1957                return "Server is not running.";
1958            } else {
1959                status = STATUS_CODE_ON;
1960                return "Server is running with process ID " + getPid() + ".";
1961            }
1962        } catch (IOException e) {
1963            status = STATUS_CODE_UNKNOWN;
1964            return "Could not check existing process (" + e.getMessage() + ").";
1965        }
1966    }
1967
1968    /**
1969     * Last status value set by {@link #status()}.
1970     */
1971    public int getStatus() {
1972        return status;
1973    }
1974
1975    /**
1976     * Last error value set by any method. Exit code values are following the Linux Standard Base Core Specification
1977     * 4.1.
1978     */
1979    public int getErrorValue() {
1980        return errorValue;
1981    }
1982
1983    /**
1984     * @throws ParseException
1985     * @return a NuxeoLauncher instance specific to current server ( Tomcat or Jetty).
1986     * @throws ConfigurationException If server cannot be identified
1987     * @since 5.5
1988     */
1989    public static NuxeoLauncher createLauncher(String[] args) throws ConfigurationException, ParseException {
1990        CommandLine cmdLine = parseOptions(args);
1991        ConfigurationGenerator cg = new ConfigurationGenerator(quiet, debug);
1992        if (cmdLine.hasOption(OPTION_HIDE_DEPRECATION)) {
1993            cg.hideDeprecationWarnings(true);
1994        }
1995        NuxeoLauncher launcher;
1996        if (cg.isJetty) {
1997            launcher = new NuxeoJettyLauncher(cg);
1998        } else if (cg.isTomcat) {
1999            launcher = new NuxeoTomcatLauncher(cg);
2000        } else {
2001            throw new ConfigurationException("Unknown server!");
2002        }
2003        launcher.setArgs(cmdLine);
2004        return launcher;
2005    }
2006
2007    /**
2008     * Sets from program arguments the launcher command and additional parameters.
2009     *
2010     * @param cmdLine Program arguments; may be used by launcher implementation. Must not be null or empty.
2011     * @throws ConfigurationException
2012     */
2013    private void setArgs(CommandLine cmdLine) throws ConfigurationException {
2014        this.cmdLine = cmdLine;
2015        extractCommandAndParams(cmdLine.getArgs());
2016        // Use GUI?
2017        if (cmdLine.hasOption(OPTION_GUI)) {
2018            useGui = Boolean.valueOf(ConnectBroker.parseAnswer(cmdLine.getOptionValue(OPTION_GUI)));
2019            log.debug("GUI: " + cmdLine.getOptionValue(OPTION_GUI) + " -> " + new Boolean(useGui).toString());
2020        } else if (OPTION_GUI.equalsIgnoreCase(command)) {
2021            useGui = true;
2022            // Shift params and extract command if there is one
2023            extractCommandAndParams(params);
2024        } else {
2025            if (PlatformUtils.isWindows()) {
2026                useGui = true;
2027                log.debug("GUI: option not set - platform is Windows -> start GUI");
2028            } else {
2029                useGui = false;
2030                log.debug("GUI: option not set - platform is not Windows -> do not start GUI");
2031            }
2032        }
2033        // Output format
2034        if (cmdLine.hasOption(OPTION_XML)) {
2035            setXMLOutput();
2036        }
2037        if (cmdLine.hasOption(OPTION_JSON)) {
2038            setJSONOutput();
2039        }
2040        if (cmdLine.hasOption(OPTION_CLID)) {
2041            try {
2042                getConnectBroker().setCLID(cmdLine.getOptionValue(OPTION_CLID));
2043            } catch (NoCLID | IOException | PackageException e) {
2044                throw new ConfigurationException(e);
2045            }
2046        }
2047    }
2048
2049    private void extractCommandAndParams(String[] args) {
2050        if (args.length > 0) {
2051            command = args[0];
2052            log.debug("Launcher command: " + command);
2053            // Command parameters
2054            if (args.length > 1) {
2055                params = Arrays.copyOfRange(args, 1, args.length);
2056                if (log.isDebugEnabled()) {
2057                    log.debug("Command parameters: " + ArrayUtils.toString(params));
2058                }
2059            } else {
2060                params = new String[0];
2061            }
2062        } else {
2063            command = null;
2064        }
2065    }
2066
2067    /**
2068     * Set launcher in quiet mode
2069     *
2070     * @since 5.5
2071     */
2072    protected static void setQuiet() {
2073        quiet = true;
2074        Log4JHelper.setQuiet(Log4JHelper.CONSOLE_APPENDER_NAME);
2075    }
2076
2077    /**
2078     * @param categories Root categories to switch DEBUG on.
2079     * @since 7.4
2080     */
2081    protected static void setDebug(String[] categories, String defaultCategory) {
2082        debug = true;
2083        if (categories == null) {
2084            categories = new String[] { defaultCategory };
2085        }
2086        Log4JHelper.setDebug(categories, true, true, new String[] { Log4JHelper.CONSOLE_APPENDER_NAME, "FILE" });
2087    }
2088
2089    /**
2090     * @param categories Root categories to switch DEBUG on.
2091     * @since 5.6
2092     */
2093    protected static void setDebug(String categories) {
2094        setDebug(categories, true);
2095    }
2096
2097    /**
2098     * @param categories Root categories to switch DEBUG on or off
2099     * @param activateDebug Set DEBUG on or off.
2100     * @since 5.6
2101     */
2102    protected static void setDebug(String categories, boolean activateDebug) {
2103        debug = activateDebug;
2104        Log4JHelper.setDebug(categories, activateDebug, true,
2105                new String[] { Log4JHelper.CONSOLE_APPENDER_NAME, "FILE" });
2106    }
2107
2108    /**
2109     * @param activateDebug if true, will activate the DEBUG logs
2110     * @since 5.5
2111     */
2112    protected static void setDebug(boolean activateDebug) {
2113        setDebug("org.nuxeo", activateDebug);
2114    }
2115
2116    /**
2117     * @param isStrict if {@code true}, set the launcher strict option
2118     * @since 7.4
2119     * @see #OPTION_STRICT_DESC
2120     */
2121    protected static void setStrict(boolean isStrict) {
2122        strict = isStrict;
2123    }
2124
2125    protected void setXMLOutput() {
2126        xmlOutput = true;
2127    }
2128
2129    protected void setJSONOutput() {
2130        jsonOutput = true;
2131        setXMLOutput();
2132    }
2133
2134    public static void printShortHelp() {
2135        System.out.println();
2136        HelpFormatter help = new HelpFormatter();
2137        help.setSyntaxPrefix("USAGE\n");
2138        help.setOptionComparator(null);
2139        help.setWidth(1000);
2140        help.printHelp(OPTION_HELP_USAGE, "OPTIONS", launcherOptions, null);
2141        System.out.println(OPTION_HELP_DESC_COMMANDS);
2142    }
2143
2144    public static void printLongHelp() {
2145        System.out.println();
2146        HelpFormatter help = new HelpFormatter();
2147        help.setSyntaxPrefix("USAGE\n");
2148        help.setOptionComparator(null);
2149        help.setWidth(1000);
2150        help.printHelp(OPTION_HELP_USAGE, OPTION_HELP_HEADER, launcherOptions, null);
2151        System.out.println(OPTION_HELP_DESC_ENV);
2152        System.out.println(OPTION_HELP_DESC_COMMANDS);
2153        System.out.println(OPTION_HELP_FOOTER);
2154    }
2155
2156    /**
2157     * Work best with current nuxeoProcess. If nuxeoProcess is null or has exited, then will try to get process ID (so,
2158     * result in that case depends on OS capabilities).
2159     *
2160     * @return true if current process is running or if a running PID is found
2161     */
2162    public boolean isRunning() {
2163        if (nuxeoProcess != null) {
2164            try {
2165                nuxeoProcess.exitValue();
2166                // Previous process has exited
2167                nuxeoProcess = null;
2168            } catch (IllegalThreadStateException exception) {
2169                return true;
2170            }
2171        }
2172        try {
2173            return (getPid() != null);
2174        } catch (IOException e) {
2175            log.error(e);
2176            return false;
2177        }
2178    }
2179
2180    /**
2181     * @since 5.5
2182     * @return true if Nuxeo finished starting
2183     */
2184    public boolean isStarted() {
2185        boolean isStarted;
2186        if (configurationGenerator.isWizardRequired()) {
2187            isStarted = isRunning();
2188        } else {
2189            try {
2190                isStarted = isRunning() && statusServletClient.isStarted();
2191            } catch (SocketTimeoutException e) {
2192                isStarted = false;
2193            }
2194        }
2195        return isStarted;
2196    }
2197
2198    /**
2199     * @return Server log file
2200     */
2201    public File getLogFile() {
2202        return new File(configurationGenerator.getLogDir(), "server.log");
2203    }
2204
2205    /**
2206     * @return Server URL
2207     */
2208    public String getURL() {
2209        return configurationGenerator.getUserConfig().getProperty(ConfigurationGenerator.PARAM_NUXEO_URL);
2210    }
2211
2212    protected ConnectBroker getConnectBroker() throws IOException, PackageException {
2213        if (connectBroker == null) {
2214            connectBroker = new ConnectBroker(configurationGenerator.getEnv());
2215            if (cmdLine.hasOption(OPTION_ACCEPT)) {
2216                connectBroker.setAccept(cmdLine.getOptionValue(OPTION_ACCEPT));
2217            }
2218            if (cmdLine.hasOption(OPTION_RELAX)) {
2219                connectBroker.setRelax(cmdLine.getOptionValue(OPTION_RELAX));
2220            }
2221            if (cmdLine.hasOption(OPTION_SNAPSHOT) || isSNAPSHOTDistribution()) {
2222                connectBroker.setAllowSNAPSHOT(true);
2223            }
2224            cset = connectBroker.getCommandSet();
2225        }
2226        return connectBroker;
2227    }
2228
2229    /**
2230     * @since 5.9.1
2231     */
2232    private boolean isSNAPSHOTDistribution() {
2233        return new Version(getDistributionInfo().version).isSnapshot();
2234    }
2235
2236    /**
2237     * List all local packages.
2238     *
2239     * @throws IOException
2240     * @throws PackageException
2241     */
2242    protected void pkgList() throws IOException, PackageException {
2243        getConnectBroker().listPending(configurationGenerator.getInstallFile());
2244        getConnectBroker().pkgList();
2245    }
2246
2247    /**
2248     * List all packages including remote ones.
2249     *
2250     * @since 5.6
2251     * @throws IOException
2252     * @throws PackageException
2253     */
2254    protected void pkgListAll() throws IOException, PackageException {
2255        getConnectBroker().listPending(configurationGenerator.getInstallFile());
2256        getConnectBroker().pkgListAll();
2257    }
2258
2259    protected boolean pkgAdd(String[] pkgNames) throws IOException, PackageException {
2260        boolean cmdOK = getConnectBroker().pkgAdd(Arrays.asList(pkgNames), cmdLine.hasOption(OPTION_IGNORE_MISSING));
2261        if (!cmdOK) {
2262            errorValue = EXIT_CODE_ERROR;
2263        }
2264        return cmdOK;
2265    }
2266
2267    protected boolean pkgInstall(String[] pkgIDs) throws IOException, PackageException {
2268        boolean cmdOK = true;
2269        if (configurationGenerator.isInstallInProgress()) {
2270            cmdOK = getConnectBroker().executePending(configurationGenerator.getInstallFile(), true,
2271                    !cmdLine.hasOption(OPTION_NODEPS));
2272        }
2273        cmdOK = cmdOK && getConnectBroker().pkgInstall(Arrays.asList(pkgIDs), cmdLine.hasOption(OPTION_IGNORE_MISSING));
2274        if (!cmdOK) {
2275            errorValue = EXIT_CODE_ERROR;
2276        }
2277        return cmdOK;
2278    }
2279
2280    protected boolean pkgUninstall(String[] pkgIDs) throws IOException, PackageException {
2281        boolean cmdOK = getConnectBroker().pkgUninstall(Arrays.asList(pkgIDs));
2282        if (!cmdOK) {
2283            errorValue = EXIT_CODE_ERROR;
2284        }
2285        return cmdOK;
2286    }
2287
2288    protected boolean pkgRemove(String[] pkgIDs) throws IOException, PackageException {
2289        boolean cmdOK = getConnectBroker().pkgRemove(Arrays.asList(pkgIDs));
2290        if (!cmdOK) {
2291            errorValue = EXIT_CODE_ERROR;
2292        }
2293        return cmdOK;
2294    }
2295
2296    protected boolean pkgReset() throws IOException, PackageException {
2297        boolean cmdOK = getConnectBroker().pkgReset();
2298        if (!cmdOK) {
2299            errorValue = EXIT_CODE_ERROR;
2300        }
2301        return cmdOK;
2302    }
2303
2304    /**
2305     * @since 5.6
2306     */
2307    protected void printInstanceXMLOutput(InstanceInfo instance) {
2308        try {
2309            JAXBContext jaxbContext = JAXBContext.newInstance(InstanceInfo.class, DistributionInfo.class,
2310                    PackageInfo.class, ConfigurationInfo.class, KeyValueInfo.class);
2311            printXMLOutput(jaxbContext, instance);
2312        } catch (JAXBException e) {
2313            log.error("Output serialization failed: " + e.getMessage());
2314            log.debug(e, e);
2315            errorValue = EXIT_CODE_NOT_RUNNING;
2316        }
2317    }
2318
2319    /**
2320     * @throws PackageException
2321     * @throws IOException
2322     * @throws ConfigurationException
2323     * @since 5.6
2324     */
2325    protected InstanceInfo showConfig() throws IOException, PackageException, ConfigurationException {
2326        InstanceInfo nxInstance = new InstanceInfo();
2327        log.info("***** Nuxeo instance configuration *****");
2328        nxInstance.NUXEO_CONF = configurationGenerator.getNuxeoConf().getPath();
2329        log.info("NUXEO_CONF: " + nxInstance.NUXEO_CONF);
2330        nxInstance.NUXEO_HOME = configurationGenerator.getNuxeoHome().getPath();
2331        log.info("NUXEO_HOME: " + nxInstance.NUXEO_HOME);
2332        // CLID
2333        try {
2334            nxInstance.clid = getConnectBroker().getCLID();
2335            log.info("Instance CLID: " + nxInstance.clid);
2336        } catch (NoCLID e) {
2337            // leave nxInstance.clid unset
2338        } catch (IOException | PackageException e) {
2339            // something went wrong in the NuxeoConnectClient initialization
2340            errorValue = EXIT_CODE_UNAUTHORIZED;
2341            throw new ConfigurationException("Could not initialize NuxeoConnectClient", e);
2342        }
2343        // distribution.properties
2344        DistributionInfo nxDistrib = getDistributionInfo();
2345        nxInstance.distribution = nxDistrib;
2346        log.info("** Distribution");
2347        log.info("- name: " + nxDistrib.name);
2348        log.info("- server: " + nxDistrib.server);
2349        log.info("- version: " + nxDistrib.version);
2350        log.info("- date: " + nxDistrib.date);
2351        log.info("- packaging: " + nxDistrib.packaging);
2352        // packages
2353        List<LocalPackage> pkgs = getConnectBroker().getPkgList();
2354        log.info("** Packages:");
2355        List<String> pkgTemplates = new ArrayList<>();
2356        for (LocalPackage pkg : pkgs) {
2357            nxInstance.packages.add(new PackageInfo(pkg));
2358            log.info(String.format("- %s (version: %s - id: %s - state: %s)", pkg.getName(), pkg.getVersion(),
2359                    pkg.getId(), pkg.getPackageState().getLabel()));
2360            // store template(s) added by this package
2361            try {
2362                File installFile = pkg.getInstallFile();
2363                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
2364                DocumentBuilder db = dbf.newDocumentBuilder();
2365                Document dom = db.parse(installFile);
2366                NodeList nodes = dom.getDocumentElement().getElementsByTagName("config");
2367                for (int i = 0; i < nodes.getLength(); i++) {
2368                    Element node = (Element) nodes.item(i);
2369                    if (node.hasAttribute("addtemplate")) {
2370                        pkgTemplates.add(node.getAttribute("addtemplate"));
2371                    }
2372                }
2373            } catch (Exception e) {
2374                log.warn("Could not parse install file for " + pkg.getName(), e);
2375            }
2376        }
2377        // nuxeo.conf
2378        ConfigurationInfo nxConfig = new ConfigurationInfo();
2379        nxConfig.dbtemplate = configurationGenerator.extractDatabaseTemplateName();
2380        log.info("** Templates:");
2381        log.info("Database template: " + nxConfig.dbtemplate);
2382        String userTemplates = configurationGenerator.getUserTemplates();
2383        StringTokenizer st = new StringTokenizer(userTemplates, ",");
2384        while (st.hasMoreTokens()) {
2385            String template = st.nextToken();
2386            if (template.equals(nxConfig.dbtemplate)) {
2387                continue;
2388            }
2389            if (pkgTemplates.contains(template)) {
2390                nxConfig.pkgtemplates.add(template);
2391                log.info("Package template: " + template);
2392            } else {
2393                File testBase = new File(configurationGenerator.getNuxeoHome(), ConfigurationGenerator.TEMPLATES
2394                        + File.separator + template);
2395                if (testBase.exists()) {
2396                    nxConfig.basetemplates.add(template);
2397                    log.info("Base template: " + template);
2398                } else {
2399                    nxConfig.usertemplates.add(template);
2400                    log.info("User template: " + template);
2401                }
2402            }
2403        }
2404        log.info("** Settings from nuxeo.conf:");
2405        CryptoProperties userConfig = configurationGenerator.getUserConfig();
2406        for (Object item : new TreeSet<>(userConfig.keySet())) {
2407            String key = (String) item;
2408            String value = userConfig.getRawProperty(key);
2409            if (key.equals("JAVA_OPTS")) {
2410                value = getJavaOptsProperty();
2411            }
2412            KeyValueInfo kv = new KeyValueInfo(key, value);
2413            nxConfig.keyvals.add(kv);
2414            if (!key.contains("password") && !key.equals(Environment.SERVER_STATUS_KEY) && !Crypto.isEncrypted(value)) {
2415                log.info(key + "=" + value);
2416            } else {
2417                log.info(key + "=********");
2418            }
2419        }
2420        nxInstance.config = nxConfig;
2421        log.info("****************************************");
2422        if (xmlOutput) {
2423            printInstanceXMLOutput(nxInstance);
2424        }
2425        return nxInstance;
2426    }
2427
2428    /**
2429     * @since 5.9.1
2430     */
2431    protected DistributionInfo getDistributionInfo() {
2432        File distFile = new File(configurationGenerator.getConfigDir(), "distribution.properties");
2433        if (!distFile.exists()) {
2434            // fallback in the file in templates
2435            distFile = new File(configurationGenerator.getNuxeoHome(), "templates");
2436            distFile = new File(distFile, "common");
2437            distFile = new File(distFile, "config");
2438            distFile = new File(distFile, "distribution.properties");
2439        }
2440        DistributionInfo nxDistrib;
2441        try {
2442            nxDistrib = new DistributionInfo(distFile);
2443        } catch (IOException e) {
2444            nxDistrib = new DistributionInfo();
2445        }
2446        return nxDistrib;
2447    }
2448
2449    /**
2450     * @since 5.6
2451     * @param pkgsToAdd
2452     * @param pkgsToInstall
2453     * @param pkgsToUninstall
2454     * @param pkgsToRemove
2455     * @return true if request execution was fine
2456     * @throws IOException
2457     * @throws PackageException
2458     */
2459    protected boolean pkgRequest(List<String> pkgsToAdd, List<String> pkgsToInstall, List<String> pkgsToUninstall,
2460            List<String> pkgsToRemove) throws IOException, PackageException {
2461        boolean cmdOK = true;
2462        if (configurationGenerator.isInstallInProgress()) {
2463            cmdOK = getConnectBroker().executePending(configurationGenerator.getInstallFile(), true, true);
2464        }
2465        cmdOK = cmdOK
2466                && getConnectBroker().pkgRequest(pkgsToAdd, pkgsToInstall, pkgsToUninstall, pkgsToRemove, true,
2467                        cmdLine.hasOption(OPTION_IGNORE_MISSING));
2468        if (!cmdOK) {
2469            errorValue = EXIT_CODE_ERROR;
2470        }
2471        return cmdOK;
2472    }
2473
2474    /**
2475     * Update the cached list of remote packages
2476     *
2477     * @since 5.6
2478     * @return true
2479     * @throws IOException
2480     * @throws PackageException
2481     */
2482    protected boolean pkgRefreshCache() throws IOException, PackageException {
2483        getConnectBroker().refreshCache();
2484        return true;
2485    }
2486
2487    /**
2488     * Add packages from the distribution to the local cache
2489     *
2490     * @throws PackageException
2491     * @throws IOException
2492     * @since 5.6
2493     */
2494    protected boolean pkgInit() throws IOException, PackageException {
2495        return getConnectBroker().addDistributionPackages();
2496    }
2497
2498    /**
2499     * Uninstall and remove all packages from the local cache
2500     *
2501     * @return {@code true} if command succeed
2502     * @throws PackageException
2503     * @throws IOException
2504     * @since 5.6
2505     */
2506    protected boolean pkgPurge() throws PackageException, IOException {
2507        return getConnectBroker().pkgPurge();
2508    }
2509
2510    /**
2511     * Install the hotfixes available for the instance
2512     *
2513     * @return {@code true} if command succeed
2514     * @throws PackageException
2515     * @throws IOException
2516     * @since 5.6
2517     */
2518    protected boolean pkgHotfix() throws IOException, PackageException {
2519        return getConnectBroker().pkgHotfix();
2520    }
2521
2522    /**
2523     * Upgrade the marketplace packages (addons) available for the instance
2524     *
2525     * @return {@code true} if command succeed
2526     * @throws PackageException
2527     * @throws IOException
2528     * @since 5.6
2529     */
2530    protected boolean pkgUpgrade() throws IOException, PackageException {
2531        return getConnectBroker().pkgUpgrade();
2532    }
2533
2534    /**
2535     * Combined install/uninstall request
2536     *
2537     * @param request Space separated list of package names or IDs prefixed with + (install) or - (uninstall)
2538     * @throws IOException
2539     * @throws PackageException
2540     * @since 5.6
2541     */
2542    protected boolean pkgCompoundRequest(List<String> request) throws IOException, PackageException {
2543        List<String> add = new ArrayList<>();
2544        List<String> install = new ArrayList<>();
2545        List<String> uninstall = new ArrayList<>();
2546        for (String param : request) {
2547            for (String subparam : param.split("[ ,]")) {
2548                if (subparam.charAt(0) == '-') {
2549                    uninstall.add(subparam.substring(1));
2550                } else if (subparam.charAt(0) == '+') {
2551                    install.add(subparam.substring(1));
2552                } else {
2553                    add.add(subparam);
2554                }
2555            }
2556        }
2557        return pkgRequest(add, install, uninstall, null);
2558    }
2559
2560    protected boolean pkgSetRequest(List<String> request, boolean nodeps) throws IOException, PackageException {
2561        boolean cmdOK;
2562        if (nodeps) {
2563            cmdOK = getConnectBroker().pkgSet(request, cmdLine.hasOption(OPTION_IGNORE_MISSING));
2564        } else {
2565            cmdOK = getConnectBroker().pkgRequest(null, request, null, null, false,
2566                    cmdLine.hasOption(OPTION_IGNORE_MISSING));
2567        }
2568        if (!cmdOK) {
2569            errorValue = EXIT_CODE_ERROR;
2570        }
2571        return cmdOK;
2572    }
2573
2574    /**
2575     * dpkg-like command which returns package location, version, dependencies, conflicts, ...
2576     *
2577     * @param packages List of packages identified by their ID, name or local filename.
2578     * @return false if unable to show package information.
2579     * @throws PackageException
2580     * @throws IOException
2581     * @since 5.7
2582     */
2583    protected boolean pkgShow(String[] packages) throws IOException, PackageException {
2584        boolean cmdOK = getConnectBroker().pkgShow(Arrays.asList(packages));
2585        if (!cmdOK) {
2586            errorValue = EXIT_CODE_ERROR;
2587        }
2588        return cmdOK;
2589    }
2590
2591}