ipLock.ProcessHandle.java Source code

Java tutorial

Introduction

Here is the source code for ipLock.ProcessHandle.java

Source

/*
 * Copyright (c) 2015 Andreas Klber
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package ipLock;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.NameFileFilter;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertEquals;

public class ProcessHandle implements SignalHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(ProcessHandle.class);

    private static final long MAX_WAIT_TIMEOUT_MS = 5000;

    private static String javaExecutablePath;

    private static String javaClasspath;

    static {
        javaExecutablePath = determineJavaExecutablePath();
        javaClasspath = determineClasspath();
    }

    private final Phaser phaser;

    private Process process;

    private ProcessBuilder pb;

    private Integer id;

    private WorkerBreakpoint currentBreakpoint;

    private SignalDispatcher signalDispatcher;

    public ProcessHandle(WorkerBreakpoint breakpoint) {
        this.id = WorkerProcessId.next();
        this.currentBreakpoint = breakpoint;

        this.phaser = new Phaser(2) {

            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                LOGGER.info("process {} reached breakpoint {}", id, currentBreakpoint.name());
                currentBreakpoint = null;

                return false;
            }
        };

        this.pb = new ProcessBuilder(javaExecutablePath, "-classpath", javaClasspath, Worker.class.getName());
        this.pb.inheritIO();

        putEnv(WorkerEnv.ID, id);
    }

    private static String determineClasspath() {
        return System.getProperty("java.class.path");
    }

    private static String determineJavaExecutablePath() {
        File javaHome = new File(System.getProperty("java.home"));

        Collection<File> files = FileUtils.listFiles(javaHome, new NameFileFilter("java"),
                new NameFileFilter("bin"));

        if (files.isEmpty()) {
            throw new RuntimeException("No java executable found at java home '" + javaHome + "'");
        }
        if (files.size() > 1) {
            throw new RuntimeException("Multiple java executables found at java home '" + javaHome + "': "
                    + StringUtils.join(files, "; "));
        }

        return Collections.min(files).getAbsolutePath();
    }

    public void putEnv(WorkerEnv var, Object val) {
        pb.environment().put(var.getVarName(), val.toString());
    }

    @Override
    public void handleSignal(Signal sig) {
        // invoked on HANDLER
        switch (sig.getCode()) {
        case CONNECT:
            break;
        case BREAKPOINT:
            arriveAndAwait();
            break;
        }
    }

    public ProcessHandle waitForBreakpoint() {
        // invoked on MAIN
        arriveAndAwait();

        return this;
    }

    private void arriveAndAwait() {
        try {
            LOGGER.info("arrive and await on phaser");
            phaser.awaitAdvanceInterruptibly(phaser.arrive(), MAX_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public ProcessHandle proceedToBreakpoint(WorkerBreakpoint breakpoint) {
        // invoked on MAIN
        activateBreakpoint(breakpoint);
        proceed();
        waitForBreakpoint();

        return this;
    }

    public ProcessHandle activateBreakpoint(WorkerBreakpoint breakpoint) {
        // invoked on MAIN
        if (this.currentBreakpoint != null) {
            throw new IllegalStateException(
                    String.format("breakpoint %s " + "is already active", breakpoint.name()));
        }
        this.currentBreakpoint = breakpoint;
        signalDispatcher.dispatch(new Signal(0, SignalCode.BREAKPOINT, breakpoint.name()));

        return this;
    }

    public ProcessHandle proceed() {
        // invoked on MAIN
        signalDispatcher.dispatch(new Signal(0, SignalCode.PROCEED));

        return this;
    }

    public ProcessHandle waitFor() {
        try {
            process.waitFor();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        return this;
    }

    public Process getProcess() {
        return process;
    }

    public Integer getId() {
        return id;
    }

    public void assertExitCode(WorkerExitCode expectedExitCode) {
        WorkerExitCode actualExitCode = WorkerExitCode.valueOf(getProcess().exitValue());
        assertEquals(String.format("Expected exit code %s, but was %s", expectedExitCode, actualExitCode),
                expectedExitCode, actualExitCode);
    }

    public void setSignalDispatcher(SignalDispatcher dispatcher) {
        this.signalDispatcher = dispatcher;
    }

    public ProcessHandle kill() {
        process.destroy();

        return this;
    }

    public ProcessHandle start() {
        try {
            this.process = pb.start();

            return this;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void destroy() {
        this.process.destroy();
    }
}