org.apache.sling.testing.serversetup.jarexec.JarExecutor.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.sling.testing.serversetup.jarexec.JarExecutor.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with this
 * work for additional information regarding copyright ownership. The ASF
 * licenses this file to You 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 org.apache.sling.testing.serversetup.jarexec;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Properties;
import java.util.regex.Pattern;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteResultHandler;
import org.apache.commons.exec.Executor;
import org.apache.commons.exec.PumpStreamHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** Start a runnable jar by forking a JVM process,
 *  and terminate the process when this VM exits.
 */
@SuppressWarnings("ALL")
public class JarExecutor {
    private final File jarToExecute;
    private final String jvmFullPath;
    private final int serverPort;
    private final Properties config;
    private Executor executor;

    private final Logger log = LoggerFactory.getLogger(getClass());

    public static final int DEFAULT_PORT = 8765;
    public static final int DEFAULT_EXIT_TIMEOUT = 30;

    public static final String DEFAULT_JAR_FOLDER = "target/dependency";
    public static final String DEFAULT_JAR_NAME_REGEXP = "org.apache.sling.*jar$";
    public static final String PROP_PREFIX = "jar.executor.";
    public static final String PROP_SERVER_PORT = PROP_PREFIX + "server.port";
    public static final String PROP_JAR_FOLDER = PROP_PREFIX + "jar.folder";
    public static final String PROP_JAR_NAME_REGEXP = PROP_PREFIX + "jar.name.regexp";
    public static final String PROP_VM_OPTIONS = PROP_PREFIX + "vm.options";
    public static final String PROP_WORK_FOLDER = PROP_PREFIX + "work.folder";
    public static final String PROP_JAR_OPTIONS = PROP_PREFIX + "jar.options";
    public static final String PROP_EXIT_TIMEOUT_SECONDS = PROP_PREFIX + "exit.timeout.seconds";
    public static final String PROP_WAIT_ONSHUTDOWN = PROP_PREFIX + "wait.on.shutdown";
    public static final String PROP_JAVA_PATH = PROP_PREFIX + "java.executable.path";
    public static final String PROP_SYNC_EXEC = PROP_PREFIX + "synchronous.exec";
    public static final String PROP_SYNC_EXEC_EXPECTED = PROP_PREFIX + "synchronous.exec.expected.result";

    @SuppressWarnings("serial")
    public static class ExecutorException extends Exception {
        ExecutorException(String reason) {
            super(reason);
        }

        ExecutorException(String reason, Throwable cause) {
            super(reason, cause);
        }
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() + ": " + jarToExecute.getName() + " (port " + serverPort + ")";
    }

    public int getServerPort() {
        return serverPort;
    }

    /** Build a JarExecutor, locate the jar to run, etc */
    public JarExecutor(Properties config) throws ExecutorException {
        this.config = config;
        final boolean isWindows = System.getProperty("os.name").toLowerCase().contains("windows");

        String portStr = config.getProperty(PROP_SERVER_PORT);
        serverPort = portStr == null ? DEFAULT_PORT : Integer.valueOf(portStr);

        final String configJvmPath = config.getProperty(PROP_JAVA_PATH);
        if (configJvmPath == null) {
            final String javaExecutable = isWindows ? "java.exe" : "java";
            jvmFullPath = System.getProperty("java.home") + File.separator + "bin" + File.separator
                    + javaExecutable;
        } else {
            jvmFullPath = configJvmPath;
        }

        String jarFolderPath = config.getProperty(PROP_JAR_FOLDER);
        jarFolderPath = jarFolderPath == null ? DEFAULT_JAR_FOLDER : jarFolderPath;
        final File jarFolder = new File(jarFolderPath);

        String jarNameRegexp = config.getProperty(PROP_JAR_NAME_REGEXP);
        jarNameRegexp = jarNameRegexp == null ? DEFAULT_JAR_NAME_REGEXP : jarNameRegexp;
        final Pattern jarPattern = Pattern.compile(jarNameRegexp);

        // Find executable jar
        final String[] candidates = jarFolder.list();
        if (candidates == null) {
            throw new ExecutorException("No files found in jar folder specified by " + PROP_JAR_FOLDER
                    + " property: " + jarFolder.getAbsolutePath());
        }
        File f = null;
        for (String filename : candidates) {
            if (jarPattern.matcher(filename).matches()) {
                f = new File(jarFolder, filename);
                break;
            }
        }

        if (f == null) {
            throw new ExecutorException("Executable jar matching '" + jarPattern + "' not found in "
                    + jarFolder.getAbsolutePath() + ", candidates are " + Arrays.asList(candidates));
        }
        jarToExecute = f;
    }

    /** Start the jar if not done yet, and setup runtime hook
     *  to stop it.
     */
    public void start() throws Exception {
        final ExecuteResultHandler h = new ExecuteResultHandler() {
            public void onProcessFailed(ExecuteException ex) {
                log.error("Process execution failed:" + ex, ex);
            }

            public void onProcessComplete(int result) {
                log.info("Process execution complete, exit code=" + result);
            }
        };

        final String vmOptions = config.getProperty(PROP_VM_OPTIONS);
        executor = new DefaultExecutor();
        final CommandLine cl = new CommandLine(jvmFullPath);
        if (vmOptions != null && vmOptions.length() > 0) {
            cl.addArguments(vmOptions);
        }
        cl.addArgument("-jar");
        cl.addArgument(jarToExecute.getAbsolutePath());

        // Additional options for the jar that's executed.
        // $JAREXEC_SERVER_PORT$ is replaced our serverPort value
        String jarOptions = config.getProperty(PROP_JAR_OPTIONS);
        if (jarOptions != null && jarOptions.length() > 0) {
            jarOptions = jarOptions.replaceAll("\\$JAREXEC_SERVER_PORT\\$", String.valueOf(serverPort));
            log.info("Executable jar options: {}", jarOptions);
            cl.addArguments(jarOptions);
        }

        final String workFolderOption = config.getProperty(PROP_WORK_FOLDER);
        if (workFolderOption != null && workFolderOption.length() > 0) {
            final File workFolder = new File(workFolderOption);
            if (!workFolder.isDirectory()) {
                throw new IOException("Work dir set by " + PROP_WORK_FOLDER + " option does not exist: "
                        + workFolder.getAbsolutePath());
            }
            log.info("Setting working directory for executable jar: {}", workFolder.getAbsolutePath());
            executor.setWorkingDirectory(workFolder);
        }

        String tmStr = config.getProperty(PROP_EXIT_TIMEOUT_SECONDS);
        final int exitTimeoutSeconds = tmStr == null ? DEFAULT_EXIT_TIMEOUT : Integer.valueOf(tmStr);

        if ("true".equals(config.getProperty(PROP_SYNC_EXEC, ""))) {
            final long start = System.currentTimeMillis();
            log.info("Executing and waiting for result: " + cl);
            final int result = executor.execute(cl);
            final int expected = Integer.valueOf(config.getProperty(PROP_SYNC_EXEC_EXPECTED, "0"));
            log.info("Execution took " + (System.currentTimeMillis() - start) + " msec");
            if (result != expected) {
                throw new ExecutorException("Expected result code " + expected + ", got " + result);
            }
        } else {
            log.info("Executing asynchronously: " + cl);
            executor.setStreamHandler(new PumpStreamHandler());
            final ShutdownHookSingleProcessDestroyer pd = new ShutdownHookSingleProcessDestroyer(
                    "java -jar " + jarToExecute.getName(), exitTimeoutSeconds);
            final boolean waitOnShutdown = Boolean.valueOf(config.getProperty(PROP_WAIT_ONSHUTDOWN, "false"));
            log.info("Setting up ProcessDestroyer with waitOnShutdown=" + waitOnShutdown);
            pd.setWaitOnShutdown(waitOnShutdown);
            executor.setProcessDestroyer(pd);
            executor.execute(cl, h);
        }
    }

    /** Stop the process that we started, if any, and wait for it to exit before returning */
    public void stop() {
        if (executor == null) {
            throw new IllegalStateException("Process not started, no Executor set");
        }
        final Object d = executor.getProcessDestroyer();
        if (d instanceof ShutdownHookSingleProcessDestroyer) {
            ((ShutdownHookSingleProcessDestroyer) d).destroyProcess(true);
            log.info("Process destroyed");
        } else {
            throw new IllegalStateException(d + " is not a Runnable, cannot destroy process");
        }
    }
}