package fitnesse.testsystems.slim;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.lang.ArrayUtils;
import fitnesse.FitNesseContext;
import fitnesse.slim.SlimPipeSocket;
import fitnesse.socketservice.ClientSocketFactory;
import fitnesse.socketservice.PlainClientSocketFactory;
import fitnesse.socketservice.PlainServerSocketFactory;
import fitnesse.socketservice.SslClientSocketFactory;
import fitnesse.testsystems.*;
import static fitnesse.slim.SlimPipeSocket.STDERR_PREFIX;
import static fitnesse.slim.SlimPipeSocket.STDOUT_PREFIX;
public class SlimClientBuilder extends ClientBuilder<SlimCommandRunningClient> {
public static final String SLIM_PORT = "SLIM_PORT";
public static final String SLIM_HOST = "SLIM_HOST";
public static final String SLIM_FLAGS = "SLIM_FLAGS";
private static final String SLIM_VERSION = "SLIM_VERSION";
public static final String MANUALLY_START_TEST_RUNNER_ON_DEBUG = "MANUALLY_START_TEST_RUNNER_ON_DEBUG";
public static final String MANUALLY_START_TEST_RUNNER = "MANUALLY_START_TEST_RUNNER";
public static final String SLIM_SSL = "SLIM_SSL";
public static final int SLIM_USE_PIPE_PORT = 1;
private static final AtomicInteger slimPortOffset = new AtomicInteger(0);
private int slimPort;
public SlimClientBuilder(Descriptor descriptor) {
super(descriptor);
slimPort = getNextSlimPort();
}
@Override
public SlimCommandRunningClient build() {
CommandRunner commandRunner = determineCommandRunner();
return new SlimCommandRunningClient(commandRunner, determineSlimHost(),
getSlimPort(), determineTimeout(), getSlimVersion(),
determineSocketFactory(commandRunner));
}
protected CommandRunner determineCommandRunner() {
if (getSlimPort() == SLIM_USE_PIPE_PORT) {
// Wrap executionLogListener
return new CommandRunner(buildCommand(),
createClasspathEnvironment(getClassPath()),
getExecutionLogListener(), determineTimeout()) {
@Override
protected void redirectOutputs(Process process, final ExecutionLogListener executionLogListener) throws IOException {
InputStream stderr = process.getErrorStream();
new Thread(new OutputReadingRunnable(stderr, new OutputWriter() {
@Override
public void write(String output) {
// Separate StdOut and StdErr and remove prefix"
String originalMsg;
originalMsg = extractOriginalMessage(output, STDOUT_PREFIX);
if (originalMsg != null) {
executionLogListener.stdOut(originalMsg);
} else {
originalMsg = extractOriginalMessage(output, STDERR_PREFIX);
if (originalMsg != null) {
executionLogListener.stdErr(originalMsg);
setCommandErrorMessage(originalMsg);
} else {
executionLogListener.stdOut(output);
}
}
}
/**
* This reverts the wrap that the LoggingOutputStream.flush method
* is doing.
*
* @param prefixedMessage
* @param level
* @return == null : the message is not prefixed with the given
* level != null : the original message content
*/
private String extractOriginalMessage(String prefixedMessage,
String level) {
if (prefixedMessage.startsWith(level))
return prefixedMessage.substring(level.length()
+ SlimPipeSocket.FOLLOWING_LINE_PREFIX.length());
return null;
}
}), "CommandRunner stdOutErr").start();
}
};
} else if (useManualStartForTestSystem()) {
return new MockCommandRunner(
"Connection to running SlimService: " + determineSlimHost() + ":"
+ getSlimPort(), getExecutionLogListener(), determineTimeout());
} else {
return new CommandRunner(buildCommand(), createClasspathEnvironment(getClassPath()), getExecutionLogListener(), determineTimeout());
}
}
protected ClientSocketFactory determineSocketFactory(CommandRunner commandRunner) {
if (getSlimPort() == SLIM_USE_PIPE_PORT) {
return new PipeBasedSocketFactory(commandRunner);
} else if ((determineClientSSLParameterClass() != null)) {
return new SslClientSocketFactory(determineHostSSLParameterClass());
} else {
return new PlainClientSocketFactory();
}
}
protected String determineClientSSLParameterClass() {
String sslParameterClassName = getVariable("slim.ssl");
if (sslParameterClassName == null) {
sslParameterClassName = getVariable(SLIM_SSL);
}
if (sslParameterClassName != null && sslParameterClassName.equalsIgnoreCase("false")) sslParameterClassName=null;
return sslParameterClassName;
}
protected String determineHostSSLParameterClass() {
return getVariable(FitNesseContext.SSL_PARAMETER_CLASS_PROPERTY);
}
public double getSlimVersion() {
double version = SlimCommandRunningClient.MINIMUM_REQUIRED_SLIM_VERSION;
try {
String slimVersion = getVariable(SLIM_VERSION);
if (slimVersion != null) {
version = Double.valueOf(slimVersion);
}
} catch (NumberFormatException e) {
// stick with default
}
return version;
}
@Override
protected String defaultTestRunner() {
return "fitnesse.slim.SlimService";
}
protected String[] buildCommand() {
String[] slimArguments = buildArguments();
String[] slimCommandPrefix = super.buildCommand(getCommandPattern(), getTestRunner(), getClassPath());
return (String[]) ArrayUtils.addAll(slimCommandPrefix, slimArguments);
}
protected String[] buildArguments() {
Object[] arguments = new String[] {};
String useSSL = determineClientSSLParameterClass();
if (useSSL != null){
arguments = ArrayUtils.add(arguments, "-ssl");
arguments = ArrayUtils.add(arguments, useSSL);
}
String[] slimFlags = getSlimFlags();
if (slimFlags != null)
for (String flag : slimFlags)
arguments = ArrayUtils.add(arguments, flag);
arguments = ArrayUtils.add(arguments, Integer.toString(getSlimPort()));
return (String[]) arguments;
}
public int getSlimPort() {
return slimPort;
}
protected void setSlimPort(int slimPort) {
this.slimPort = slimPort;
}
private int findFreePort() {
int port;
try {
ServerSocket socket = new PlainServerSocketFactory().createServerSocket(0);
port = socket.getLocalPort();
socket.close();
} catch (Exception e) {
port = -1;
}
return port;
}
protected int getNextSlimPort() {
final int base = getSlimPortBase();
final int poolSize = getSlimPortPoolSize();
if (base == 0) {
return findFreePort();
}
if (base == SLIM_USE_PIPE_PORT)
return SLIM_USE_PIPE_PORT;
synchronized (SlimClientBuilder.class) {
int offset = slimPortOffset.get();
int port = offset + base;
offset = (offset + 1) % poolSize;
slimPortOffset.set(offset);
// is port available??
return port;
}
}
public static void clearSlimPortOffset() {
slimPortOffset.set(0);
}
private int getSlimPortBase() {
try {
String port = getVariable("slim.port");
if (port == null) {
port = getVariable(SLIM_PORT);
}
if (port != null) {
return Integer.parseInt(port);
}
} catch (NumberFormatException e) {
// stick with default
}
return SLIM_USE_PIPE_PORT;
}
private int getSlimPortPoolSize() {
try {
String poolSize = getVariable("slim.pool.size");
if (poolSize != null) {
return Integer.parseInt(poolSize);
}
} catch (NumberFormatException e) {
// stick with default
}
return 10;
}
protected String determineSlimHost() {
String slimHost = getVariable("slim.host");
if (slimHost == null) {
slimHost = getVariable(SLIM_HOST);
}
return slimHost == null ? "localhost" : slimHost;
}
protected String[] getSlimFlags() {
String slimFlags = getVariable("slim.flags");
if (slimFlags == null) {
slimFlags = getVariable(SLIM_FLAGS);
}
return slimFlags == null ? new String[] {} : parseCommandLine(slimFlags);
}
protected int determineTimeout() {
if (isDebug()) {
try {
String debugTimeout = getVariable("slim.debug.timeout");
if (debugTimeout != null) {
return Integer.parseInt(debugTimeout);
}
} catch (NumberFormatException e) {
// stick with default
}
}
try {
String timeout = getVariable("slim.timeout");
if (timeout != null) {
return Integer.parseInt(timeout);
}
} catch (NumberFormatException e) {
// stick with default
}
return 10;
}
private boolean useManualStartForTestSystem() {
if (isDebug()) {
String useManualStart = getVariable("manually.start.test.runner.on.debug");
if (useManualStart == null) {
useManualStart = getVariable(MANUALLY_START_TEST_RUNNER_ON_DEBUG);
}
if (useManualStart != null) {
return "true".equalsIgnoreCase(useManualStart);
}
}
String useManualStart = getVariable("manually.start.test.runner");
if (useManualStart == null) {
useManualStart = getVariable(MANUALLY_START_TEST_RUNNER);
}
return "true".equalsIgnoreCase(useManualStart);
}
}