/* * Copyright © 2016 Cask Data, Inc. * * 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 co.cask.cdap; import com.google.common.base.Charsets; import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.AbstractExecutionThreadService; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.annotation.Nullable; /** * Executor that helps with launching an external Java process. */ final class ExternalJavaProcessExecutor extends AbstractExecutionThreadService { private static final Logger LOG = LoggerFactory.getLogger(ExternalJavaProcessExecutor.class); private static final long SHUTDOWN_TIMEOUT_SECONDS = 5; private static final String SEPARATOR = System.getProperty("file.separator"); private static final String BASE_CLASSPATH = System.getProperty("java.class.path"); private static final String JAVA_PATH = System.getProperty("java.home") + SEPARATOR + "bin" + SEPARATOR + "java"; private final String classPath; private final String className; private final List<String> args; private final Map<String, String> processEnv; private ExecutorService executor; private Process process; private Thread shutdownThread; ExternalJavaProcessExecutor(String className) { this(className, Collections.<String>emptyList()); } ExternalJavaProcessExecutor(String className, List<String> args) { this(className, args, Collections.<String, String>emptyMap(), null); } ExternalJavaProcessExecutor(String className, List<String> args, Map<String, String> processEnv, @Nullable String additionalClassPath) { this.classPath = (additionalClassPath == null) ? BASE_CLASSPATH : String.format( "%s:%s", additionalClassPath, BASE_CLASSPATH); this.className = className; this.args = args; this.processEnv = Objects.firstNonNull(processEnv, ImmutableMap.<String, String>of()); } @Override protected void startUp() throws Exception { executor = Executors.newSingleThreadExecutor(new ThreadFactoryBuilder().setDaemon(true).setNameFormat( String.format("external-java-process-%s", className)).build()); shutdownThread = createShutdownThread(); List<String> command = ImmutableList.<String>builder().add(JAVA_PATH, "-cp", classPath, className) .addAll(args).build(); ProcessBuilder processBuilder = new ProcessBuilder(command); processBuilder.environment().putAll(processEnv); processBuilder.redirectErrorStream(true); process = processBuilder.start(); LOG.info("Process {} started", className); executor.execute(createLogRunnable(process)); } @Override protected void run() throws Exception { int exitCode = process.waitFor(); if (exitCode != 0) { LOG.error("Process {} exited with exit code {}", className, exitCode); } } @Override protected void triggerShutdown() { executor.shutdownNow(); shutdownThread.start(); } @Override protected void shutDown() throws Exception { shutdownThread.interrupt(); shutdownThread.join(); } private Thread createShutdownThread() { Thread thread = new Thread(String.format("shutdown-%s", className)) { @Override public void run() { process.destroy(); } }; thread.setDaemon(true); return thread; } private Runnable createLogRunnable(final Process process) { return new Runnable() { @Override public void run() { try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), Charsets.UTF_8))) { String line = reader.readLine(); while (!Thread.currentThread().isInterrupted() && line != null) { LOG.info(line); line = reader.readLine(); } } catch (IOException e) { LOG.error("Exception when reading from stderr stream for {}.", ExternalJavaProcessExecutor.class); } } }; } }