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