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