/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.application.process;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.function.Supplier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.process.AllProcessesCommands;
import org.sonar.process.ProcessCommands;
import static java.lang.String.format;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_KEY;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT;
public class JavaProcessLauncherImpl implements JavaProcessLauncher {
private static final Logger LOG = LoggerFactory.getLogger(JavaProcessLauncherImpl.class);
private final File tempDir;
private final AllProcessesCommands allProcessesCommands;
private final Supplier<SystemProcessBuilder> processBuilderSupplier;
public JavaProcessLauncherImpl(File tempDir) {
this(tempDir, new AllProcessesCommands(tempDir), SystemProcessBuilder::new);
}
JavaProcessLauncherImpl(File tempDir, AllProcessesCommands allProcessesCommands, Supplier<SystemProcessBuilder> processBuilderSupplier) {
this.tempDir = tempDir;
this.allProcessesCommands = allProcessesCommands;
this.processBuilderSupplier = processBuilderSupplier;
}
@Override
public void close() {
allProcessesCommands.close();
}
@Override
public ProcessMonitor launch(JavaCommand javaCommand) {
Process process = null;
ProcessCommands commands;
try {
commands = allProcessesCommands.createAfterClean(javaCommand.getProcessId().getIpcIndex());
SystemProcessBuilder processBuilder = create(javaCommand);
LOG.info("Launch process[{}]: {}", javaCommand.getProcessId().getKey(), String.join(" ", processBuilder.command()));
process = processBuilder.start();
return new ProcessMonitorImpl(process, commands);
} catch (Exception e) {
// just in case
if (process != null) {
process.destroyForcibly();
}
throw new IllegalStateException(format("Fail to launch process [%s]", javaCommand.getProcessId().getKey()), e);
}
}
private SystemProcessBuilder create(JavaCommand javaCommand) {
List<String> commands = new ArrayList<>();
commands.add(buildJavaPath());
commands.addAll(javaCommand.getJavaOptions());
// TODO warning - does it work if temp dir contains a whitespace ?
// TODO move to JavaCommandFactory ?
commands.add(format("-Djava.io.tmpdir=%s", tempDir.getAbsolutePath()));
commands.addAll(buildClasspath(javaCommand));
commands.add(javaCommand.getClassName());
commands.add(buildPropertiesFile(javaCommand).getAbsolutePath());
SystemProcessBuilder processBuilder = processBuilderSupplier.get();
processBuilder.command(commands);
processBuilder.directory(javaCommand.getWorkDir());
processBuilder.environment().putAll(javaCommand.getEnvVariables());
processBuilder.redirectErrorStream(true);
return processBuilder;
}
private static String buildJavaPath() {
String separator = System.getProperty("file.separator");
return new File(new File(System.getProperty("java.home")), "bin" + separator + "java").getAbsolutePath();
}
private static List<String> buildClasspath(JavaCommand javaCommand) {
String pathSeparator = System.getProperty("path.separator");
return Arrays.asList("-cp", String.join(pathSeparator, javaCommand.getClasspath()));
}
private File buildPropertiesFile(JavaCommand javaCommand) {
File propertiesFile = null;
try {
propertiesFile = File.createTempFile("sq-process", "properties", tempDir);
Properties props = new Properties();
props.putAll(javaCommand.getArguments());
props.setProperty(PROPERTY_PROCESS_KEY, javaCommand.getProcessId().getKey());
props.setProperty(PROPERTY_PROCESS_INDEX, Integer.toString(javaCommand.getProcessId().getIpcIndex()));
// FIXME is it the responsibility of child process to have this timeout (too) ?
props.setProperty(PROPERTY_TERMINATION_TIMEOUT, "60000");
props.setProperty(PROPERTY_SHARED_PATH, tempDir.getAbsolutePath());
try (OutputStream out = new FileOutputStream(propertiesFile)) {
props.store(out, format("Temporary properties file for command [%s]", javaCommand.getProcessId().getKey()));
}
return propertiesFile;
} catch (Exception e) {
throw new IllegalStateException("Cannot write temporary settings to " + propertiesFile, e);
}
}
}