package io.vertx.core.impl.launcher.commands;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.cli.CLIException;
import io.vertx.core.cli.CommandLine;
import io.vertx.core.cli.annotations.*;
import io.vertx.core.impl.VertxInternal;
import io.vertx.core.impl.launcher.VertxLifecycleHooks;
import io.vertx.core.json.JsonObject;
import io.vertx.core.spi.launcher.ExecutionContext;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Name("run")
@Summary("Runs a verticle called <main-verticle> in its own instance of vert.x.")
public class RunCommand extends BareCommand {
protected DeploymentOptions deploymentOptions;
protected boolean cluster;
protected boolean ha;
protected int instances;
protected String config;
protected boolean worker;
protected String mainVerticle;
protected List<String> redeploy;
protected String vertxApplicationBackgroundId;
protected String onRedeployCommand;
protected Watcher watcher;
private long redeployScanPeriod;
private long redeployGracePeriod;
private long redeployTerminationPeriod;
@Option(longName = "ha", acceptValue = false, flag = true)
@Description("If specified the verticle will be deployed as a high availability (HA) deployment. This means it can " +
"fail over to any other nodes in the cluster started with the same HA group.")
public void setHighAvailability(boolean ha) {
this.ha = ha;
}
@Option(longName = "cluster", acceptValue = false, flag = true)
@Description("If specified then the vert.x instance will form a cluster with any other vert.x instances on the " +
"network.")
public void setCluster(boolean cluster) {
this.cluster = cluster;
}
@Option(longName = "worker", acceptValue = false)
@Description("If specified then the verticle is a worker verticle.")
public void setWorker(boolean worker) {
this.worker = worker;
}
@Option(longName = "instances", argName = "instances")
@DefaultValue("1")
@Description("Specifies how many instances of the verticle will be deployed. Defaults to 1.")
public void setInstances(int instances) {
this.instances = instances;
}
@Option(longName = "conf", argName = "config")
@Description("Specifies configuration that should be provided to the verticle. <config> should reference either a " +
"text file containing a valid JSON object which represents the configuration OR be a JSON string.")
public void setConfig(String configuration) {
if (configuration != null) {
this.config = configuration.trim()
.replaceAll("^\"|\"$", "")
.replaceAll("^'|'$", "");
} else {
this.config = null;
}
}
@Argument(index = 0, argName = "main-verticle", required = true)
@Description("The main verticle to deploy, it can be a fully qualified class name or a file.")
public void setMainVerticle(String verticle) {
this.mainVerticle = verticle;
}
@Option(longName = "redeploy", argName = "includes")
@Description("Enable automatic redeployment of the application. This option takes a set on includes as parameter " +
"indicating which files need to be watched. Patterns are separated by a comma.")
@ParsedAsList
public void setRedeploy(List<String> redeploy) {
this.redeploy = redeploy;
}
@Option(longName = "onRedeploy", argName = "cmd")
@Description("Optional shell command executed when a redeployment is triggered (deprecated - will be removed in 3" +
".3, use 'on-redeploy' instead")
@Hidden
@Deprecated
public void setOnRedeployCommandOld(String command) {
out.println("[WARNING] the 'onRedeploy' option is deprecated, and will be removed in vert.x 3.3. Use " +
"'on-redeploy' instead.");
setOnRedeployCommand(command);
}
@Option(longName = "on-redeploy", argName = "cmd")
@Description("Optional shell command executed when a redeployment is triggered")
public void setOnRedeployCommand(String command) {
this.onRedeployCommand = command;
}
@Option(longName = "redeploy-scan-period", argName = "period")
@Description("When redeploy is enabled, this option configures the file system scanning period to detect file " +
"changes. The time is given in milliseconds. 250 ms by default.")
@DefaultValue("250")
public void setRedeployScanPeriod(long period) {
this.redeployScanPeriod = period;
}
@Option(longName = "redeploy-grace-period", argName = "period")
@Description("When redeploy is enabled, this option configures the grace period between 2 redeployments. The time " +
"is given in milliseconds. 1000 ms by default.")
@DefaultValue("1000")
public void setRedeployGracePeriod(long period) {
this.redeployGracePeriod = period;
}
@Option(longName = "redeploy-termination-period", argName = "period")
@Description("When redeploy is enabled, this option configures the time waited to be sure that the previous " +
"version of the application has been stopped. It is useful on Windows, where the 'terminate' command may take time to be " +
"executed.The time is given in milliseconds. 0 ms by default.")
@DefaultValue("0")
public void setRedeployStopWaitingTime(long period) {
this.redeployTerminationPeriod = period;
}
@Override
public void setUp(ExecutionContext context) throws CLIException {
super.setUp(context);
CommandLine commandLine = executionContext.commandLine();
if (!isClustered() && (
commandLine.isOptionAssigned(executionContext.cli().getOption("cluster-host"))
|| commandLine.isOptionAssigned(executionContext.cli().getOption("cluster-port"))
|| commandLine.isOptionAssigned(executionContext.cli().getOption("cluster-public-host"))
|| commandLine.isOptionAssigned(executionContext.cli().getOption("cluster-public-port"))
)) {
throw new CLIException("The -cluster-xxx options require -cluster to be enabled");
}
io.vertx.core.cli.Option haGroupOption = executionContext.cli().getOption("hagroup");
io.vertx.core.cli.Option quorumOption = executionContext.cli().getOption("quorum");
if (!ha &&
(commandLine.isOptionAssigned(haGroupOption) || commandLine.isOptionAssigned(quorumOption))) {
throw new CLIException("The option -hagroup and -quorum requires -ha to be enabled");
}
}
@Override
public boolean isClustered() {
return cluster || ha;
}
@Override
public boolean getHA() {
return ha;
}
@Override
public void run() {
if (redeploy == null || redeploy.isEmpty()) {
JsonObject conf = getConfiguration();
if (conf == null) {
conf = new JsonObject();
}
afterConfigParsed(conf);
super.run(this::afterStoppingVertx);
if (vertx == null) {
ExecUtils.exitBecauseOfVertxInitializationIssue();
}
if (vertx instanceof VertxInternal) {
((VertxInternal) vertx).addCloseHook(completionHandler -> {
try {
beforeStoppingVertx(vertx);
completionHandler.handle(Future.succeededFuture());
} catch (Exception e) {
completionHandler.handle(Future.failedFuture(e));
}
});
}
deploymentOptions = new DeploymentOptions();
configureFromSystemProperties(deploymentOptions, DEPLOYMENT_OPTIONS_PROP_PREFIX);
deploymentOptions.setConfig(conf).setWorker(worker).setHa(ha).setInstances(instances);
beforeDeployingVerticle(deploymentOptions);
deploy();
} else {
initializeRedeployment();
}
}
protected synchronized void initializeRedeployment() {
if (watcher != null) {
throw new IllegalStateException("Redeployment already started ? The watcher already exists");
}
vertxApplicationBackgroundId = UUID.randomUUID().toString() + "-redeploy";
watcher = new Watcher(getCwd(), redeploy,
this::startAsBackgroundApplication,
this::stopBackgroundApplication,
onRedeployCommand,
redeployGracePeriod,
redeployScanPeriod);
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
shutdownRedeployment();
}
});
watcher.watch();
}
protected synchronized void shutdownRedeployment() {
if (watcher != null) {
watcher.close();
watcher = null;
}
}
protected synchronized void stopBackgroundApplication(Handler<Void> onCompletion) {
executionContext.execute("stop", vertxApplicationBackgroundId, "--redeploy");
if (redeployTerminationPeriod > 0) {
try {
Thread.sleep(redeployTerminationPeriod);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
if (onCompletion != null) {
onCompletion.handle(null);
}
}
protected void startAsBackgroundApplication(Handler<Void> onCompletion) {
List<String> args = new ArrayList<>();
args.add("run");
args.add("--vertx-id=" + vertxApplicationBackgroundId);
args.addAll(executionContext.commandLine().allArguments());
if (cluster) {
args.add("--cluster");
}
if (clusterHost != null) {
args.add("--cluster-host=" + clusterHost);
}
if (clusterPort != 0) {
args.add("--cluster-port=" + clusterPort);
}
if (clusterPublicHost != null) {
args.add("--cluster-public-host=" + clusterPublicHost);
}
if (clusterPublicPort != -1) {
args.add("--cluster-public-port=" + clusterPublicPort);
}
if (ha) {
args.add("--ha");
}
if (haGroup != null && !haGroup.equals("__DEFAULT__")) {
args.add("--hagroup=" + haGroup);
}
if (quorum != -1) {
args.add("--quorum=" + quorum);
}
if (classpath != null && !classpath.isEmpty()) {
args.add("--classpath=" + classpath.stream().collect(Collectors.joining(File.pathSeparator)));
}
if (config != null) {
args.add("--conf");
args.add(config);
}
if (instances != 1) {
args.add("--instances=" + instances);
}
if (worker) {
args.add("--worker");
}
if (systemProperties != null) {
args.addAll(systemProperties.stream().map(s -> "-D" + s).collect(Collectors.toList()));
}
args.add("--redirect-output");
executionContext.execute("start", args.toArray(new String[0]));
if (onCompletion != null) {
onCompletion.handle(null);
}
}
protected void deploy() {
deploy(mainVerticle, vertx, deploymentOptions, res -> {
if (res.failed()) {
handleDeployFailed(res.cause());
}
});
}
private void handleDeployFailed(Throwable cause) {
if (executionContext.main() instanceof VertxLifecycleHooks) {
((VertxLifecycleHooks) executionContext.main()).handleDeployFailed(vertx, mainVerticle, deploymentOptions, cause);
} else {
ExecUtils.exitBecauseOfVertxDeploymentIssue();
}
}
protected JsonObject getConfiguration() {
return getJsonFromFileOrString(config, "conf");
}
protected void beforeDeployingVerticle(DeploymentOptions deploymentOptions) {
final Object main = executionContext.main();
if (main instanceof VertxLifecycleHooks) {
((VertxLifecycleHooks) main).beforeDeployingVerticle(deploymentOptions);
}
}
protected void afterConfigParsed(JsonObject config) {
final Object main = executionContext.main();
if (main instanceof VertxLifecycleHooks) {
((VertxLifecycleHooks) main).afterConfigParsed(config);
}
}
protected void beforeStoppingVertx(Vertx vertx) {
final Object main = executionContext.main();
if (main instanceof VertxLifecycleHooks) {
((VertxLifecycleHooks) main).beforeStoppingVertx(vertx);
}
}
protected void afterStoppingVertx() {
final Object main = executionContext.main();
if (main instanceof VertxLifecycleHooks) {
((VertxLifecycleHooks) main).afterStoppingVertx();
}
}
}