de.flapdoodle.embed.process.runtime.AbstractProcess.java Source code

Java tutorial

Introduction

Here is the source code for de.flapdoodle.embed.process.runtime.AbstractProcess.java

Source

/**
 * Copyright (C) 2011
 *   Michael Mosmann <michael@mosmann.de>
 *   Martin Jhren <m.joehren@googlemail.com>
 *
 * with contributions from
 *    konstantin-ba@github,
   Archimedes Trajano (trajano@github),
   Kevin D. Keck (kdkeck@github),
   Ben McCann (benmccann@github)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package de.flapdoodle.embed.process.runtime;

import de.flapdoodle.embed.process.config.IExecutableProcessConfig;
import de.flapdoodle.embed.process.config.RuntimeConfig;
import de.flapdoodle.embed.process.config.io.ProcessOutput;
import de.flapdoodle.embed.process.distribution.Distribution;
import de.flapdoodle.embed.process.extract.ExtractedFileSet;
import de.flapdoodle.embed.process.io.Processors;
import de.flapdoodle.embed.process.io.StreamToLineProcessor;
import de.flapdoodle.embed.process.io.file.Files;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class AbstractProcess<T extends IExecutableProcessConfig, E extends Executable<T, P>, P extends IStopable>
        implements IStopable {

    private static final Logger logger = LoggerFactory.getLogger(AbstractProcess.class);

    public static final int TIMEOUT = 20000;

    private final T config;
    private final RuntimeConfig runtimeConfig;
    private final E executable;
    private ProcessControl process;
    private long processId;

    private boolean stopped = false;
    private boolean registeredJobKiller;

    private final Distribution distribution;

    private final File pidFile;

    public AbstractProcess(Distribution distribution, T config, RuntimeConfig runtimeConfig, E executable)
            throws IOException {
        this.config = config;
        this.runtimeConfig = runtimeConfig;
        this.executable = executable;
        this.distribution = distribution;
        // pid file needs to be set before ProcessBuilder is called
        this.pidFile = pidFile(this.executable.getFile().executable());

        ProcessOutput outputConfig = runtimeConfig.getProcessOutput();

        // Refactor me - to much things done in this try/catch
        String nextCall = "";
        try {

            nextCall = "onBeforeProcess()";

            onBeforeProcess(runtimeConfig);

            nextCall = "newProcessBuilder()";

            ProcessBuilder processBuilder = ProcessControl.newProcessBuilder(
                    runtimeConfig.getCommandLinePostProcessor().process(distribution,
                            getCommandLine(distribution, config, this.executable.getFile())),
                    getEnvironment(distribution, config, this.executable.getFile()), true);

            nextCall = "onBeforeProcessStart()";

            onBeforeProcessStart(processBuilder, config, runtimeConfig);

            nextCall = "start()";

            process = ProcessControl.start(config.supportConfig(), processBuilder);

            nextCall = "writePidFile()";

            if (process.getPid() != null) {
                writePidFile(pidFile, process.getPid());
            }

            nextCall = "addShutdownHook()";

            if (runtimeConfig.isDaemonProcess() && !executable.isRegisteredJobKiller()) {
                ProcessControl.addShutdownHook(new JobKiller());
                registeredJobKiller = true;
            }

            nextCall = "onAfterProcessStart()";

            onAfterProcessStart(process, runtimeConfig);

        } catch (IOException iox) {
            logger.error("failed to call {}", nextCall, iox);
            logger.info("construct {}", config.toString());
            stop();
            throw iox;
        }
    }

    @Override
    public boolean isRegisteredJobKiller() {
        return registeredJobKiller;
    }

    protected File pidFile(File executableFile) {
        return new File(executableFile.getParentFile(), executableBaseName(executableFile.getName()) + ".pid");
    }

    protected File pidFile() {
        return pidFile;
    }

    private String executableBaseName(String name) {
        int idx = name.lastIndexOf('.');
        if (idx != -1) {
            return name.substring(0, idx);
        }
        return name;
    }

    public T getConfig() {
        return config;
    }

    protected void onBeforeProcess(RuntimeConfig runtimeConfig) {

    }

    protected void onBeforeProcessStart(ProcessBuilder processBuilder, T config, RuntimeConfig runtimeConfig) {

    }

    protected void onAfterProcessStart(ProcessControl process, RuntimeConfig runtimeConfig) {
        ProcessOutput outputConfig = runtimeConfig.getProcessOutput();
        Processors.connect(process.getReader(), outputConfig.getOutput());
        Processors.connect(process.getError(), StreamToLineProcessor.wrap(outputConfig.getError()));
    }

    protected abstract List<String> getCommandLine(Distribution distribution, T config, ExtractedFileSet exe)
            throws IOException;

    protected Map<String, String> getEnvironment(Distribution distribution, T config, ExtractedFileSet exe) {
        // default implementation, override to provide your own environment
        return new HashMap<>();
    }

    @Override
    public synchronized final void stop() {
        if (!stopped) {
            stopped = true;
            stopInternal();
            onAfterProcessStop(this.config, this.runtimeConfig);
            cleanupInternal();
            if (!Files.forceDelete(pidFile)) {
                logger.warn("Could not delete pid file: {}", pidFile);
            }
        }
    }

    protected abstract void stopInternal();

    protected abstract void cleanupInternal();

    protected void onAfterProcessStop(T config, RuntimeConfig runtimeConfig) {

    }

    protected final void stopProcess() {
        if (process != null)
            process.stop();
    }

    public int waitFor() throws InterruptedException {
        return process.waitFor();
    }

    protected void setProcessId(long processId) {
        this.processId = processId;
    }

    protected boolean sendKillToProcess() {
        return getProcessId() > 0 && Processes.killProcess(config.supportConfig(), distribution.platform(),
                StreamToLineProcessor.wrap(runtimeConfig.getProcessOutput().getCommands()), getProcessId());
    }

    protected boolean sendTermToProcess() {
        return getProcessId() > 0 && Processes.termProcess(config.supportConfig(), distribution.platform(),
                StreamToLineProcessor.wrap(runtimeConfig.getProcessOutput().getCommands()), getProcessId());
    }

    protected boolean tryKillToProcess() {
        return getProcessId() > 0 && Processes.tryKillProcess(config.supportConfig(), distribution.platform(),
                StreamToLineProcessor.wrap(runtimeConfig.getProcessOutput().getCommands()), getProcessId());
    }

    public boolean isProcessRunning() {
        return getProcessId() > 0 && Processes.isProcessRunning(distribution.platform(), getProcessId());
    }

    public long getProcessId() {
        Long pid = process.getPid();
        return pid != null ? pid : processId;
    }

    /**
     *
     */
    class JobKiller implements Runnable {

        @Override
        public void run() {
            AbstractProcess.this.stop();
        }
    }

    protected static int getPidFromFile(File pidFile) throws IOException {
        // wait for file to be created
        int tries = 0;
        while (!pidFile.exists() && tries < 5) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e1) {
                // ignore
            }
            logger.warn("Didn't find pid file in try {}, waiting 100ms...", tries);
            tries++;
        }
        // don't check file to be there. want to throw IOException if
        // something happens
        if (!pidFile.exists()) {
            throw new IOException("Could not find pid file " + pidFile);
        }

        // read the file, wait for the pid string to appear
        String fileContent = StringUtils
                .chomp(StringUtils.strip(new String(java.nio.file.Files.readAllBytes(pidFile.toPath()))));
        tries = 0;
        while (StringUtils.isBlank(fileContent) && tries < 5) {
            fileContent = StringUtils
                    .chomp(StringUtils.strip(new String(java.nio.file.Files.readAllBytes(pidFile.toPath()))));
            try {
                Thread.sleep(100);
            } catch (InterruptedException e1) {
                // ignore
            }
            tries++;
        }
        // check for empty file
        if (StringUtils.isBlank(fileContent)) {
            throw new IOException(
                    "Pidfile " + pidFile + "does not contain a pid. Waited for " + tries * 100 + "ms.");
        }
        // pidfile exists and has content
        try {
            return Integer.parseInt(fileContent);
        } catch (NumberFormatException e) {
            throw new IOException("Pidfile " + pidFile + "does not contain a valid pid. Content: " + fileContent);
        }
    }

    protected void writePidFile(File pidFile, long pid) throws IOException {
        Files.write(pid + "\n", pidFile);
    }
}