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