All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.vertx.ext.shell.command.impl.ProcessImpl Maven / Gradle / Ivy

There is a newer version: 5.0.0.CR1
Show newest version
/*
 * Copyright 2015 Red Hat, Inc.
 *
 *  All rights reserved. This program and the accompanying materials
 *  are made available under the terms of the Eclipse Public License v1.0
 *  and Apache License v2.0 which accompanies this distribution.
 *
 *  The Eclipse Public License is available at
 *  http://www.eclipse.org/legal/epl-v10.html
 *
 *  The Apache License v2.0 is available at
 *  http://www.opensource.org/licenses/apache2.0.php
 *
 *  You may elect to redistribute this code under either of these licenses.
 *
 *
 * Copyright (c) 2015 The original author or authors
 * ------------------------------------------------------
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * and Apache License v2.0 which accompanies this distribution.
 *
 *     The Eclipse Public License is available at
 *     http://www.eclipse.org/legal/epl-v10.html
 *
 *     The Apache License v2.0 is available at
 *     http://www.opensource.org/licenses/apache2.0.php
 *
 * You may elect to redistribute this code under either of these licenses.
 *
 */

package io.vertx.ext.shell.command.impl;

import io.vertx.core.Context;
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.ext.shell.cli.CliToken;
import io.vertx.ext.shell.command.Command;
import io.vertx.ext.shell.command.CommandProcess;
import io.vertx.ext.shell.session.Session;
import io.vertx.ext.shell.system.*;
import io.vertx.ext.shell.system.Process;
import io.vertx.ext.shell.term.Tty;

import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Julien Viet
 */
public class ProcessImpl implements Process {

  private final Vertx vertx;
  private final Context context;
  private final Context processContext;
  private final Command commandContext;
  private final Handler handler;
  private final List args;
  private Tty tty;
  private Session session;
  private Handler interruptHandler;
  private Handler suspendHandler;
  private Handler resumeHandler;
  private Handler endHandler;
  private Handler backgroundHandler;
  private Handler foregroundHandler;
  private Handler terminatedHandler;

  // State exposed to the command, updated on the command context
  private boolean foreground;

  // Internal state used by the process
  private ExecStatus processStatus;
  private boolean processForeground;
  private Handler stdinHandler;
  private Handler resizeHandler;
  private Integer exitCode;

  public ProcessImpl(Vertx vertx, Context context, Command commandContext, List args, Handler handler) {
    this.vertx = vertx;
    this.context = context;
    this.commandContext = commandContext;
    this.handler = handler;
    this.args = args;
    processContext = vertx.getOrCreateContext();
    processStatus = ExecStatus.READY;
  }

  @Override
  public Integer exitCode() {
    return exitCode;
  }

  @Override
  public ExecStatus status() {
    return processStatus;
  }

  @Override
  public synchronized Process setTty(Tty tty) {
    this.tty = tty;
    return this;
  }

  @Override
  public synchronized Tty getTty() {
    return tty;
  }

  @Override
  public synchronized Process setSession(Session session) {
    this.session = session;
    return this;
  }

  @Override
  public synchronized Session getSession() {
    return session;
  }

  @Override
  public Process terminatedHandler(Handler handler) {
    terminatedHandler = handler;
    return this;
  }

  @Override
  public boolean interrupt(Handler completionHandler) {
    if (processStatus == ExecStatus.RUNNING || processStatus == ExecStatus.STOPPED) {
      Handler handler = interruptHandler;
      processContext.runOnContext(v -> {
        try {
          if (handler != null) {
            handler.handle(null);
          }
        } finally {
          if (completionHandler != null) {
            processContext.runOnContext(completionHandler);
          }
        }
      });
      return handler != null;
    } else {
      throw new IllegalStateException("Cannot interrupt process in " + processStatus + " state");
    }
  }

  @Override
  public synchronized void resume(boolean fg, Handler completionHandler) {
    if (processStatus == ExecStatus.STOPPED) {
      updateStatus(
          ExecStatus.RUNNING,
          null,
          fg,
          resumeHandler,
          terminatedHandler,
          completionHandler);
    } else {
      throw new IllegalStateException("Cannot resume process in " + processStatus + " state");
    }
  }

  @Override
  public synchronized void suspend(Handler completionHandler) {
    if (processStatus == ExecStatus.RUNNING) {
      updateStatus(
          ExecStatus.STOPPED,
          null,
          false,
          suspendHandler,
          terminatedHandler,
          completionHandler);
    } else {
      throw new IllegalStateException("Cannot suspend process in " + processStatus + " state");
    }
  }

  @Override
  public void toBackground(Handler completionHandler) {
    if (processStatus == ExecStatus.RUNNING) {
      if (processForeground) {
        updateStatus(
            ExecStatus.RUNNING,
            null,
            false,
            backgroundHandler,
            terminatedHandler,
            completionHandler);
      }
    } else {
      throw new IllegalStateException("Cannot set to background a process in " + processStatus + " state");
    }
  }

  @Override
  public void toForeground(Handler completionHandler) {
    if (processStatus == ExecStatus.RUNNING) {
      if (!processForeground) {
        updateStatus(
            ExecStatus.RUNNING,
            null,
            true,
            foregroundHandler,
            terminatedHandler,
            completionHandler);
      }
    } else {
      throw new IllegalStateException("Cannot set to foreground a process in " + processStatus + " state");
    }
  }

  @Override
  public void terminate(Handler completionHandler) {
    if (!terminate(-10, completionHandler)) {
      throw new IllegalStateException("Cannot terminate terminated process");
    }
  }

  private synchronized boolean terminate(int exitCode, Handler completionHandler) {
    if (processStatus != ExecStatus.TERMINATED) {
      updateStatus(
          ExecStatus.TERMINATED,
          exitCode,
          false,
          endHandler,
          terminatedHandler,
          completionHandler);
      return true;
    } else {
      return false;
    }
  }

  private void updateStatus(ExecStatus statusUpdate, Integer exitCodeUpdate, boolean foregroundUpdate, Handler handler, Handler terminatedHandler, Handler completionHandler) {
    processStatus = statusUpdate;
    exitCode = exitCodeUpdate;
    if (!foregroundUpdate) {
      if (processForeground) {
        processForeground = false;
        if (stdinHandler != null) {
          tty.stdinHandler(null);
        }
        if (resizeHandler != null) {
          tty.resizehandler(null);
        }
      }
    } else {
      if (!processForeground) {
        processForeground = true;
        if (stdinHandler != null) {
          tty.stdinHandler(stdinHandler);
        }
        if (resizeHandler != null) {
          tty.resizehandler(resizeHandler);
        }
      }
    }
    context.runOnContext(v -> {
      foreground = foregroundUpdate;
      try {
        if (handler != null) {
          handler.handle(null);
        }
      } finally {
        if (completionHandler != null) {
          processContext.runOnContext(completionHandler);
        }
      }
    });
    if (terminatedHandler != null && statusUpdate == ExecStatus.TERMINATED) {
      processContext.runOnContext(v -> terminatedHandler.handle(exitCodeUpdate));
    }
  }

  @Override
  public synchronized void run(boolean fg) {
    if (processStatus != ExecStatus.READY) {
      throw new IllegalStateException("Cannot run proces in " + processStatus + " state");
    }
    processStatus = ExecStatus.RUNNING;
    processForeground = fg;
    foreground = fg;

    // Make a local copy
    Tty tty = this.tty;
    if (tty == null) {
      throw new IllegalStateException("Cannot execute process without a TTY set");
    }

    CommandLine cl;
    final List args2 = args.stream().filter(CliToken::isText).map(CliToken::value).collect(Collectors.toList());
    if (commandContext.cli() != null) {

      //
      try {
        // Build to skip validation problems
        if (commandContext.cli().parse(args2, false).isAskingForHelp()) {
          StringBuilder usage = new StringBuilder();
          commandContext.cli().usage(usage);
          usage.append('\n');
          tty.write(usage.toString());
          terminate();
          return;
        }

        cl = commandContext.cli().parse(args2);
      } catch (CLIException e) {
        tty.write(e.getMessage() + "\n");
        terminate();
        return;
      }
    } else {
      cl = null;
    }

    CommandProcess process = new CommandProcess() {

      @Override
      public Vertx vertx() {
        return vertx;
      }

      @Override
      public String type() {
        return tty.type();
      }

      @Override
      public CommandLine commandLine() {
        return cl;
      }

      @Override
      public List argsTokens() {
        return args;
      }

      @Override
      public List args() {
        return args2;
      }

      @Override
      public boolean isForeground() {
        return foreground;
      }

      @Override
      public Session session() {
        return session;
      }

      @Override
      public int width() {
        return tty.width();
      }

      @Override
      public int height() {
        return tty.height();
      }

      @Override
      public CommandProcess stdinHandler(Handler handler) {
        if (handler != null) {
          stdinHandler = data -> context.runOnContext(v -> handler.handle(data));
        } else {
          stdinHandler = null;
        }
        if (processForeground && stdinHandler != null) {
          tty.stdinHandler(stdinHandler);
        }
        return this;
      }

      @Override
      public CommandProcess write(String data) {
        synchronized (ProcessImpl.this) {
          if (processStatus != ExecStatus.RUNNING) {
            throw new IllegalStateException("Cannot write to standard output when " + status().name().toLowerCase());
          }
        }
        processContext.runOnContext(v -> tty.write(data));
        return this;
      }

      @Override
      public CommandProcess resizehandler(Handler handler) {
        if (handler != null) {
          resizeHandler = v -> context.runOnContext(handler::handle);
        } else {
          resizeHandler = null;
        }
        tty.resizehandler(resizeHandler);
        return this;
      }

      @Override
      public CommandProcess interruptHandler(Handler handler) {
        synchronized (ProcessImpl.this) {
          interruptHandler = handler;
        }
        return this;
      }

      @Override
      public CommandProcess suspendHandler(Handler handler) {
        synchronized (ProcessImpl.this) {
          suspendHandler = handler;
        }
        return this;
      }

      @Override
      public CommandProcess resumeHandler(Handler handler) {
        synchronized (ProcessImpl.this) {
          resumeHandler = handler;
        }
        return this;
      }

      @Override
      public CommandProcess endHandler(Handler handler) {
        synchronized (ProcessImpl.this) {
          endHandler = handler;
        }
        return this;
      }

      @Override
      public CommandProcess backgroundHandler(Handler handler) {
        synchronized (ProcessImpl.this) {
          backgroundHandler = handler;
        }
        return this;
      }

      @Override
      public CommandProcess foregroundHandler(Handler handler) {
        synchronized (ProcessImpl.this) {
          foregroundHandler = handler;
        }
        return this;
      }

      @Override
      public void end() {
        end(0);
      }

      @Override
      public void end(int statusCode) {
        terminate(statusCode, null);
      }

    };

    //
    context.runOnContext(v -> {
      try {
        handler.handle(process);
      } catch (Throwable e) {
        terminate(1, null);
        throw e;
      }
    });
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy