Package com.subhajit.managedprocess

Source Code of com.subhajit.managedprocess.ManagedProcess$StartupResult

package com.subhajit.managedprocess;

import static com.subhajit.processmanager.api.IHeartbeat.INTERVAL;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ExecutionException;

import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;

import org.apache.tools.ant.taskdefs.ManifestException;

import com.subhajit.build.Manifest;
import com.subhajit.codeanalysis.distribution.DistributionManager;
import com.subhajit.common.util.StrUtils;
import com.subhajit.common.util.process.ProcessRunner;
import com.subhajit.common.util.streams.NetUtils;
import com.subhajit.processmanager.agent.ProcessManagerMain;
import com.subhajit.processmanager.agent.heartbeat.Heartbeat;
import com.subhajit.processmanager.agent.heartbeat.HeartbeatRepositoryImpl;
import com.subhajit.processmanager.api.IHeartbeat;
import com.subhajit.processmanager.api.IHeartbeatRepository;

/**
* An operating-system process that can be remotely managed.
*
* @author sdasgupta
*
*/
public class ManagedProcess {
  private static final long TWICE_THE_INTERVAL = 10 * INTERVAL;
  private final String uid;
  private final IHeartbeatRepository repo;
  private ManagedProcessStatus status;
  private final ProcessBuilder processBuilder;
  private final String description;

  public ManagedProcess(String description, ProcessBuilder processBuilder)
      throws IOException {
    super();
    this.description = description;
    uid = UUID.randomUUID().toString();
    repo = new HeartbeatRepositoryImpl(uid);
    status = ManagedProcessStatus.UNKNOWN;
    this.processBuilder = copy(processBuilder);
    ManagedProcesses.getDefaultInstance().add(this);
  }

  private static ProcessBuilder copy(ProcessBuilder processBuilder) {
    ProcessBuilder ret = new ProcessBuilder();
    ret.command(processBuilder.command());
    ret.directory(processBuilder.directory());
    ret.environment().putAll(processBuilder.environment());
    return ret;
  }

  public static final class StartupResult implements Serializable {
    private static final long serialVersionUID = 1L;
    private final String pid;
    private final OutputStream standardOutput;
    private final OutputStream standardError;

    public StartupResult(String pid, OutputStream standardOutput,
        OutputStream standardError) {
      super();
      this.pid = pid;
      this.standardOutput = standardOutput;
      this.standardError = standardError;
    }

    public String getPid() {
      return pid;
    }

    public OutputStream getStandardOutput() {
      return standardOutput;
    }

    public OutputStream getStandardError() {
      return standardError;
    }
  }

  /**
   * Convenience method invokes
   * {@link ManagedProcess#startup(OutputStream, OutputStream)} and returns a
   * {@link StartupResult}.
   *
   * @return
   * @throws IOException
   * @throws InterruptedException
   * @throws ClassNotFoundException
   * @throws ManifestException
   * @throws ExecutionException
   */
  public synchronized StartupResult startup() throws IOException,
      InterruptedException, ClassNotFoundException, ManifestException,
      ExecutionException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    ByteArrayOutputStream err = new ByteArrayOutputStream();
    return new StartupResult(startup(out, err), out, err);
  }

  /**
   * Launches the process described by {@link ManagedProcess#processBuilder}
   * and returns a process id that can be used to identify the process.
   *
   * <p>
   * <tt>builder</tt> must satisfy the following requirements:
   * <li><tt>builder.command()</tt> must not be <tt>null</tt> and
   * <tt>builder.command().size() > 2</tt></li>
   * <li>builder.directory() must not be <tt>null</tt> and must exist.</li>
   * </p>
   *
   * @param builder
   * @param out
   * @param err
   * @return
   * @throws IOException
   * @throws InterruptedException
   * @throws ClassNotFoundException
   * @throws ManifestException
   * @throws ExecutionException
   */
  public synchronized String startup(OutputStream out, OutputStream err)
      throws IOException, InterruptedException, ClassNotFoundException,
      ManifestException, ExecutionException {
    ProcessBuilder builder = copy(processBuilder);
    // Check valid state before attempting to launch the process.
    boolean valid = status == ManagedProcessStatus.UNKNOWN
        || status == ManagedProcessStatus.STOPPED;
    if (!valid) {
      throw new IllegalStateException(
          "Cannot launch, since status is neither "
              + ManagedProcessStatus.STOPPED + " nor "
              + ManagedProcessStatus.UNKNOWN);
    }

    final List<String> command = new ArrayList<String>(builder.command());
    if (command.size() < 2) {
      throw new IllegalArgumentException("Bad builder command : size < 2");
    }

    // Add the remote debugging arguments.
    int remoteDebuggerPort = NetUtils.findFreePort(10000) + 1;
    String debuggingOption = StrUtils
        .strReplace(
            "-Xdebug -Xrunjdwp:transport=dt_socket,address=${port},server=y,suspend=n",
            "${port}", "" + remoteDebuggerPort);
    command.add(1, "-Xdebug " + debuggingOption + " ");
    if (ProcessRunner.isWindows()) {
      command.add(0, "start \"" + description + "\" /LOW /MIN ");
    }

    final File dir = builder.directory();
    if (dir == null) {
      throw new IllegalArgumentException("Run directory not given.");
    }

    // Export the process-manager.jar file to the "dir" directory.
    Class<?> klass = ProcessManagerMain.class;
    Manifest manifest = new Manifest();
    manifest.getMainSection().addConfiguredAttribute(
        new Manifest.Attribute("Premain-Class",
            ProcessManagerMain.class.getName()));
    DistributionManager.createDistributionWithAdditionalClasses(Thread
        .currentThread().getContextClassLoader(), new File(dir,
        "process-manager.jar"), manifest, klass.getName(),
        com.subhajit.processmanager.agent.Process.class.getName(),
        HeartbeatRepositoryImpl.class.getName(), Heartbeat.class
            .getName());
    command.add(2, "-javaagent:process-manager.jar");

    command.add(2, "-Duid=" + uid);
    if ( ProcessRunner.isLinux() || ProcessRunner.isSolaris() ){
      command.add("&");
    }
    builder.command(command);

    // Launch the process.
    ProcessRunner.execViaShell(builder, out, err, true);
    status = ManagedProcessStatus.STARTING;
    return uid;
  }

  /**
   * Returns the last heart beat written by the managed process.
   *
   * @param millis
   * @return
   * @throws InterruptedException
   */
  public IHeartbeat getHeartbeat(final long millis)
      throws InterruptedException {
    final List<IHeartbeat> holder = new ArrayList<IHeartbeat>();
    Thread thread = new Thread(new Runnable() {
      public void run() {
        try {
          IHeartbeat ret = repo.getLatest(uid);
          if (ret == null) {
            try {
              Thread.sleep(millis);
            } catch (InterruptedException e) {
              Thread.currentThread().interrupt();
              return;
            }
            ret = repo.getLatest(uid);
            if (ret != null) {
              holder.add(ret);
            }
            return;
          } else {
            holder.add(ret);
            return;
          }
        } catch (IOException exc) {
        }
      }
    });
    thread.start();
    thread.join();
    if (holder.isEmpty()) {
      return null;
    } else {
      return holder.get(0);
    }
  }

  public String getUid() {
    return uid;
  }

  /**
   * Issues a <tt>shutdown</tt> command to the managed process and returns
   * immediately.
   *
   * @param millis
   * @return {@link ManagedProcessStatus#UNKNOWN} if the heart beat for the
   *         process is not obtained within <tt>millis</tt> milli-seconds, or
   *         {@link ManagedProcessStatus#STOPPING} if a a <tt>stop</tt>
   *         command was issued to the server.
   * @throws InterruptedException
   * @throws IOException
   * @throws InstanceNotFoundException
   * @throws MalformedObjectNameException
   * @throws MBeanException
   * @throws ReflectionException
   * @throws NullPointerException
   */
  public ManagedProcessStatus shutdown(long millis)
      throws InterruptedException, IOException,
      InstanceNotFoundException, MalformedObjectNameException,
      MBeanException, ReflectionException, NullPointerException {
    if (status != ManagedProcessStatus.RUNNING) {
      throw new IllegalStateException("Cannot stop process in state - "
          + status.toString());
    }
    IHeartbeat heartbeat = getHeartbeat(millis);
    if (heartbeat == null) {
      return ManagedProcessStatus.UNKNOWN;
    }
    JMXServiceURL url = new JMXServiceURL(heartbeat.getManagementUrl());
    JMXConnector connector = null;
    try {
      connector = JMXConnectorFactory.connect(url);
      connector.getMBeanServerConnection().invoke(
          new ObjectName("launcher", "id", "bean"), "shutdown",
          new Object[0], new String[] {});
      return ManagedProcessStatus.STOPPING;
    } catch (IOException exc) {
      return ManagedProcessStatus.STOPPING;
    } finally {
      if (connector != null) {
        try {
          connector.close();
        } catch (IOException exc) {
          exc.printStackTrace();
        }
      }
    }
  }

  @Override
  public boolean equals(Object o) {
    if (o == null) {
      return false;
    }
    if (this == o) {
      return true;
    }
    if (o instanceof ManagedProcess) {
      ManagedProcess other = (ManagedProcess) o;
      return uid.equals(other.uid);
    } else {
      return false;
    }
  }

  @Override
  public int hashCode() {
    return uid.hashCode();
  }

  public ManagedProcessStatus getStatus() throws InterruptedException {
    ManagedProcessStatus status = updateStatus();
    synchronized (this) {
      this.status = status;
    }
    return status;
  }

  /**
   * Updates {@link #status}.
   *
   * @return
   * @throws InterruptedException
   */
  private ManagedProcessStatus updateStatus() throws InterruptedException {
    switch (status) {
    case UNKNOWN:
      return handleUnknownStatus();

    case RUNNING:
      return handleRunningStatus();

    case START_FAILED:
      return handleStartFailedStatus();

    case STARTING:
      return handleStartingStatus();

    case STOPPED:
      return handleStoppedStatus();

    case STOPPING:
      return handleStoppingStatus();

    default: // This is most likely a programming error.
      throw new AssertionError("Unknown status - " + status);
    }
  }

  private ManagedProcessStatus handleStoppingStatus()
      throws InterruptedException {
    IHeartbeat heartbeat = getHeartbeat(INTERVAL);
    if (heartbeat == null) {
      heartbeat = getHeartbeat(TWICE_THE_INTERVAL);
      if (heartbeat == null) {
        return ManagedProcessStatus.STOPPED;
      } else {
        // Still stopping.
        return ManagedProcessStatus.STOPPING;
      }
    } else {
      if (isCurrent(heartbeat)) {
        return ManagedProcessStatus.STOPPING;
      } else {
        heartbeat = getHeartbeat(TWICE_THE_INTERVAL);
        if (heartbeat == null) {
          return ManagedProcessStatus.STOPPED;
        } else if (isCurrent(heartbeat, TWICE_THE_INTERVAL)) {
          return ManagedProcessStatus.STOPPING;
        } else {
          return ManagedProcessStatus.STOPPED;
        }
      }
    }
  }

  private ManagedProcessStatus handleStoppedStatus() {
    // Nothing to do.
    return ManagedProcessStatus.STOPPED;
  }

  /**
   * Called after {@link #startup(ProcessBuilder, OutputStream, OutputStream)}
   * has been invoked.
   *
   * @return
   * @throws InterruptedException
   */
  private ManagedProcessStatus handleStartingStatus()
      throws InterruptedException {
    IHeartbeat heartbeat = getHeartbeat(INTERVAL);
    if (heartbeat == null) {
      heartbeat = getHeartbeat(TWICE_THE_INTERVAL);
      if (heartbeat == null) {
        return ManagedProcessStatus.START_FAILED;
      } else {
        return ManagedProcessStatus.RUNNING;
      }
    } else {
      if (isCurrent(heartbeat)) {
        return ManagedProcessStatus.RUNNING;
      } else {
        heartbeat = getHeartbeat(TWICE_THE_INTERVAL);
        if (heartbeat == null) {
          return ManagedProcessStatus.START_FAILED;
        } else {
          return ManagedProcessStatus.RUNNING;
        }
      }
    }
  }

  private boolean isCurrent(IHeartbeat heartbeat, long... intervals) {
    long testInterval = intervals.length > 0 ? intervals[0] : INTERVAL;
    return (System.currentTimeMillis() - heartbeat.getTime()) < testInterval;
  }

  private ManagedProcessStatus handleStartFailedStatus()
      throws InterruptedException {
    // Nothing to do.
    return handleStartingStatus();
  }

  private ManagedProcessStatus handleRunningStatus()
      throws InterruptedException {
    IHeartbeat heartbeat = getHeartbeat(INTERVAL);
    if (heartbeat == null) {
      heartbeat = getHeartbeat(TWICE_THE_INTERVAL);
      if (heartbeat == null) {
        return ManagedProcessStatus.STOPPED;
      } else {
        // The process missed a beat.
        return ManagedProcessStatus.RUNNING;
      }
    } else {
      if (isCurrent(heartbeat)) {
        return ManagedProcessStatus.RUNNING;
      } else {
        heartbeat = getHeartbeat(TWICE_THE_INTERVAL);
        if (heartbeat == null) {
          return ManagedProcessStatus.STOPPED;
        } else if (!isCurrent(heartbeat, TWICE_THE_INTERVAL)) {
          // The process crashed.
          return ManagedProcessStatus.STOPPED;
        } else {
          return ManagedProcessStatus.RUNNING;
        }
      }
    }
  }

  /**
   * Happens when {@link #startup(ProcessBuilder, OutputStream, OutputStream)}
   * has not been invoked for the first time, or the first heartbeat has not
   * been detected.
   *
   * @return
   * @throws InterruptedException
   */
  private ManagedProcessStatus handleUnknownStatus()
      throws InterruptedException {
    // Nothing to do.
    // return ManagedProcessStatus.UNKNOWN;
    IHeartbeat heartbeat = getHeartbeat(INTERVAL);
    if (heartbeat == null) {
      heartbeat = getHeartbeat(TWICE_THE_INTERVAL);
      if (heartbeat == null) {
        return ManagedProcessStatus.UNKNOWN;
      } else {
        return ManagedProcessStatus.RUNNING;
      }
    } else {
      if (isCurrent(heartbeat)) {
        return ManagedProcessStatus.RUNNING;
      } else {
        heartbeat = getHeartbeat(TWICE_THE_INTERVAL);
        if (heartbeat == null) {
          return ManagedProcessStatus.UNKNOWN;
        } else {
          return ManagedProcessStatus.RUNNING;
        }
      }
    }
  }

  public ManagedProcessStatus forceStop() {
    if (status == ManagedProcessStatus.START_FAILED) {
      status = ManagedProcessStatus.STOPPED;
      return status;
    } else {
      throw new IllegalStateException(
          "Force stop command not valid in this state - " + status);
    }
  }

  @Override
  public String toString() {
    return getDescription() + " - " + uid + "\t{"
        + processBuilder.toString() + "}";
  }

  public String getDescription() {
    return description;
  }
}
TOP

Related Classes of com.subhajit.managedprocess.ManagedProcess$StartupResult

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.