blob: d4fb2ccb28b3a6167b0065e61be4c108d67d98f4 [file] [log] [blame]
/*
* 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.maven.slingstart.run;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.lang.ProcessBuilder.Redirect;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import org.apache.commons.io.IOUtils;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.shared.utils.StringUtils;
import org.apache.sling.maven.slingstart.launcher.Main;
/**
* A callable for launchpad an instance
*/
public class LauncherCallable implements Callable<ProcessDescription> {
private final LaunchpadEnvironment environment;
private final ServerConfiguration configuration;
private final Log logger;
public LauncherCallable(final Log logger,
final ServerConfiguration configuration,
final LaunchpadEnvironment environment) {
this.logger = logger;
this.configuration = configuration;
this.environment = environment;
}
/**
* @see java.util.concurrent.Callable#call()
*/
@Override
public ProcessDescription call() throws Exception {
// fail if launchpad with this id is already started
if (!ProcessDescriptionProvider.getInstance().isRunConfigurationAvailable(configuration.getId())) {
throw new Exception("Launchpad with id " + configuration.getId() + " is not available");
}
// get the launchpad jar
final File launchpad = this.environment.prepare(this.configuration.getFolder());
// Lock the launchpad id
final String launchpadKey = ProcessDescriptionProvider.getInstance().getId(configuration.getId());
// start launchpad
ProcessDescription cfg = this.start(launchpad);
// Add thread hook to shutdown launchpad
if (environment.isShutdownOnExit()) {
cfg.installShutdownHook();
}
// Add configuration to the config provider
ProcessDescriptionProvider.getInstance().addRunConfiguration(cfg, launchpadKey);
boolean started = false;
try {
final long endTime = System.currentTimeMillis() + this.environment.getReadyTimeOutSec() * 1000;
boolean finished = false;
while ( !started && !finished && System.currentTimeMillis() < endTime ) {
Thread.sleep(5000);
started = cfg.getControlListener().isStarted();
try {
// if we get an exit value, the process has stopped
cfg.getProcess().exitValue();
finished = true;
} catch ( final IllegalThreadStateException itse) {
// everything as expected
}
}
if ( finished ) {
throw new Exception("Launchpad did exit unexpectedly.");
}
if ( !started ) {
throw new Exception("Launchpad did not start successfully in " + this.environment.getReadyTimeOutSec() + " seconds.");
}
this.logger.info("Started Launchpad '" + configuration.getId() +
"' at port " + configuration.getPort()+ " [run modes: " + configuration.getRunmode()+ "]");
} finally {
// stop control port
cfg.getControlListener().stop();
// call launchpad stop routine if not properly started
if (!started) {
stop(this.logger, cfg);
ProcessDescriptionProvider.getInstance().removeRunConfiguration(cfg.getId());
cfg = null;
}
}
return cfg;
}
public boolean isRunning() {
return getControlPortFile(this.configuration.getFolder()).exists();
}
private void add(final List<String> args, final String value) {
if ( value != null ) {
final String[] single = value.trim().split(" ");
for(final String v : single) {
if ( v.trim().length() > 0 ) {
args.add(v.trim());
}
}
}
}
private ProcessDescription start(final File jar) throws Exception {
final ProcessDescription cfg = new ProcessDescription(this.configuration.getId(), this.configuration.getFolder());
final ProcessBuilder builder = new ProcessBuilder();
final List<String> args = new ArrayList<String>();
args.add("java");
add(args, this.configuration.getVmOpts());
add(args, this.configuration.getVmDebugOpts(this.environment.getDebug()));
args.add("-cp");
args.add("bin");
args.add(Main.class.getName());
// first three arguments: jar, listener port, verbose
args.add(jar.getPath());
args.add(String.valueOf(cfg.getControlListener().getPort()));
args.add("true");
// from here on launchpad properties
add(args, this.configuration.getOpts());
final String contextPath = this.configuration.getContextPath();
if ( contextPath != null && contextPath.length() > 0 && !contextPath.equals("/") ) {
args.add("-r");
args.add(contextPath);
}
if ( this.configuration.getPort() != null ) {
args.add("-p");
args.add(this.configuration.getPort());
}
if ( this.configuration.getControlPort() != null ) {
args.add("-j");
args.add(this.configuration.getControlPort());
}
if ( this.configuration.getRunmode() != null && this.configuration.getRunmode().length() > 0 ) {
args.add("-Dsling.run.modes=" + this.configuration.getRunmode());
}
if ( !this.environment.isShutdownOnExit() ) {
args.add("start");
}
builder.command(args.toArray(new String[args.size()]));
builder.directory(this.configuration.getFolder());
builder.redirectErrorStream(true);
logger.info("Starting Launchpad " + this.configuration.getId() + "...");
String stdOutFile = this.configuration.getStdOutFile();
if (StringUtils.isNotBlank(stdOutFile)) {
File absoluteStdOutFile = new File(builder.directory(), stdOutFile);
// make sure to create the parent directories (if they do not exist yet)
absoluteStdOutFile.getParentFile().mkdirs();
builder.redirectOutput(absoluteStdOutFile);
logger.info("Redirecting stdout and stderr to " + absoluteStdOutFile);
} else {
builder.redirectOutput(Redirect.INHERIT);
}
logger.debug("Launchpad cmd: " + builder.command());
logger.debug("Launchpad dir: " + builder.directory());
try {
cfg.setProcess(builder.start());
} catch (final IOException e) {
if (cfg.getProcess() != null) {
cfg.getProcess().destroy();
cfg.setProcess(null);
}
throw new Exception("Could not start the Launchpad", e);
}
return cfg;
}
public static void stop(final Log LOG, final ProcessDescription cfg) throws Exception {
boolean isNew = false;
if (cfg.getProcess() != null || isNew ) {
LOG.info("Stopping Launchpad '" + cfg.getId() + "'");
boolean destroy = true;
final int twoMinutes = 2 * 60 * 1000;
final File controlPortFile = getControlPortFile(cfg.getDirectory());
LOG.debug("Control port file " + controlPortFile + " exists: " + controlPortFile.exists());
if ( controlPortFile.exists() ) {
// reading control port
int controlPort = -1;
String secretKey = null;
LineNumberReader lnr = null;
String serverName = null;
try {
lnr = new LineNumberReader(new FileReader(controlPortFile));
final String portLine = lnr.readLine();
final int pos = portLine.indexOf(':');
controlPort = Integer.parseInt(portLine.substring(pos + 1));
if ( pos > 0 ) {
serverName = portLine.substring(0, pos);
}
secretKey = lnr.readLine();
} catch ( final NumberFormatException ignore) {
// we ignore this
LOG.debug("Error reading control port file " + controlPortFile, ignore);
} catch ( final IOException ignore) {
// we ignore this
LOG.debug("Error reading control port file " + controlPortFile, ignore);
} finally {
IOUtils.closeQuietly(lnr);
}
if ( controlPort != -1 ) {
final List<String> hosts = new ArrayList<String>();
if ( serverName != null ) {
hosts.add(serverName);
}
hosts.add("localhost");
hosts.add("127.0.0.1");
LOG.debug("Found control port " + controlPort);
int index = 0;
while ( destroy && index < hosts.size() ) {
final String hostName = hosts.get(index);
Socket clientSocket = null;
DataOutputStream out = null;
BufferedReader in = null;
try {
LOG.debug("Trying to connect to " + hostName + ":" + controlPort);
clientSocket = new Socket();
// set a socket timeout
clientSocket.connect(new InetSocketAddress(hostName, controlPort), twoMinutes);
// without that, read() call on the InputStream associated with this Socket is infinite
clientSocket.setSoTimeout(twoMinutes);
LOG.debug(hostName + ":" + controlPort + " connection estabilished, sending the 'stop' command...");
out = new DataOutputStream(clientSocket.getOutputStream());
in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
if (secretKey != null) {
out.writeBytes(secretKey);
out.write(' ');
}
out.writeBytes("stop\n");
in.readLine();
destroy = false;
LOG.debug("'stop' command sent to " + hostName + ":" + controlPort);
} catch (final Throwable ignore) {
// catch Throwable because InetSocketAddress and Socket#connect throws unchecked exceptions
// we ignore this for now
LOG.debug("Error sending 'stop' command to " + hostName + ":" + controlPort + " due to: " + ignore.getMessage());
} finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
IOUtils.closeQuietly(clientSocket);
}
index++;
}
}
}
if ( cfg.getProcess() != null ) {
final Process process = cfg.getProcess();
if (!destroy) {
// as shutdown might block forever, we use a timeout
final long now = System.currentTimeMillis();
final long end = now + twoMinutes;
LOG.debug("Waiting for process to stop...");
while (isAlive(process) && (System.currentTimeMillis() < end)) {
try {
Thread.sleep(2500);
} catch (InterruptedException e) {
// ignore
}
}
if (isAlive( process)) {
LOG.debug("Process timeout out after 2 minutes");
destroy = true;
} else {
LOG.debug("Process stopped");
}
}
if (destroy) {
LOG.debug("Destroying process...");
process.destroy();
LOG.debug("Process destroyed");
}
cfg.setProcess(null);
}
} else {
LOG.warn("Launchpad already stopped");
}
}
private static boolean isAlive(Process process) {
try {
process.exitValue();
return false;
} catch (IllegalThreadStateException e) {
return true;
}
}
private static File getControlPortFile(final File directory) {
final File launchpadDir = new File(directory, LaunchpadEnvironment.WORK_DIR_NAME);
final File confDir = new File(launchpadDir, "conf");
final File controlPortFile = new File(confDir, "controlport");
return controlPortFile;
}
}