All Downloads are FREE. Search and download functionalities are using the official Maven repository.

fitnesse.testsystems.slim.SlimCommandRunningClient Maven / Gradle / Ivy

// Copyright (C) 2003-2009 by Object Mentor, Inc. All rights reserved.
// Released under the terms of the CPL Common Public License version 1.0.
package fitnesse.testsystems.slim;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import util.FileUtil;
import fitnesse.slim.SlimError;
import fitnesse.slim.SlimStreamReader;
import fitnesse.slim.SlimVersion;
import fitnesse.slim.instructions.Instruction;
import fitnesse.slim.protocol.SlimDeserializer;
import fitnesse.slim.protocol.SlimListBuilder;
import fitnesse.slim.protocol.SlimSerializer;
import fitnesse.socketservice.ClientSocketFactory;
import fitnesse.testsystems.CommandRunner;
import fitnesse.util.Clock;

public class SlimCommandRunningClient implements SlimClient {
  private static final Logger LOG = Logger.getLogger(SlimCommandRunningClient.class.getName());

  public static final int NO_SLIM_SERVER_CONNECTION_FLAG = -32000;
  public static double MINIMUM_REQUIRED_SLIM_VERSION = 0.3;

  protected final CommandRunner slimRunner;
  private final int connectionTimeout;
  private final double requiredSlimVersion;
  private final ClientSocketFactory clientSocketFactory;
  private Socket client;
  protected SlimStreamReader reader;
  protected OutputStream writer;
  private String slimServerVersionMessage;
  private double slimServerVersion;
  private String hostName;
  private int port;

  public SlimCommandRunningClient(CommandRunner slimRunner, String hostName, int port, int connectionTimeout, double requiredSlimVersion, ClientSocketFactory clientSocketFactory) {
    this.slimRunner = slimRunner;
    this.hostName = hostName;
    this.port = port;
    this.connectionTimeout = connectionTimeout;
    this.requiredSlimVersion = requiredSlimVersion;
    this.clientSocketFactory = clientSocketFactory;
  }

  @Override
  public void start() throws IOException, SlimVersionMismatch {
    try {
      slimRunner.asynchronousStart();
    } catch (Exception e) {
      final String slimErrorMessage = "Error SLiM server startup failed. "
          + slimRunner.getCommandErrorMessage();
      throw new SlimError(slimErrorMessage, e);

    }
    connect();
    checkForVersionMismatch();
  }

  private void checkForVersionMismatch() throws SlimVersionMismatch {
    double serverVersionNumber = getServerVersion();
    if (serverVersionNumber == NO_SLIM_SERVER_CONNECTION_FLAG) {
      throw new SlimVersionMismatch("Slim Protocol Version Error: Server did not respond with a valid version number.");
    } else if (serverVersionNumber < requiredSlimVersion) {
      throw new SlimVersionMismatch(String.format("Slim Protocol Version Error: Expected V%s but was V%s", requiredSlimVersion, serverVersionNumber));
    }
  }

  @Override
  public void kill() {
    if (slimRunner != null)
      slimRunner.kill();
    FileUtil.close(reader);
    FileUtil.close(writer);
    FileUtil.close(client);
  }

  @Override
  public void connect() throws IOException {
    final int sleepStep = 50; // milliseconds
    long timeOut = Clock.currentTimeInMillis() + connectionTimeout * 1000;
    LOG.finest("Trying to connect to host: " + hostName + " on port: " + port + " timeout setting: " + connectionTimeout);
    while (client == null) {
      if (slimRunner != null && slimRunner.isDead()) {
        final String slimErrorMessage = "Error SLiM server died before a connection could be established. " + slimRunner.getCommandErrorMessage();
      	throw new SlimError(slimErrorMessage);
      }
      try {
        client = clientSocketFactory.createSocket(hostName, port);
      } catch (IOException e) {
        if (Clock.currentTimeInMillis() > timeOut) {
          throw new SlimError("Error connecting to SLiM server on " + hostName + ":" + port, e);
        } else {
          try {
            Thread.sleep(sleepStep);
          } catch (InterruptedException e1) {
            Thread.currentThread().interrupt();
          }
        }
      }
    }
    LOG.fine("Connected to host: " + hostName + " on port: " + port + " timeout setting: " + connectionTimeout);

    reader = SlimStreamReader.getReader(client);
    writer = SlimStreamReader.getByteWriter(client);

    // Convert seconds to milliseconds
    int waittime = connectionTimeout * 1000;
    int oldTimeout = client.getSoTimeout();
    client.setSoTimeout(waittime);
    try {
      validateConnection();
    } finally {
      client.setSoTimeout(oldTimeout);
    }
  }

  protected void validateConnection() throws IOException {
    long timeOut = Clock.currentTimeInMillis() + connectionTimeout * 1000;
    LOG.finest("Trying to get SlimHeader: " + " timeout setting: "
        + connectionTimeout);
    while (!isConnected()) {
      if (slimRunner != null && slimRunner.isDead()) {
        final String slimErrorMessage = "Error SLiM server died before Header Message could be read. "
            + slimRunner.getCommandErrorMessage();
        throw new SlimError(slimErrorMessage);
      }
      if (Clock.currentTimeInMillis() > timeOut) {
        throw new SlimError(
            "Timeout while reading slim header from client. Check that you are connecting to the right port and that the slim client is running. You can increase the timeout limit by setting 'slim.timeout' in the fitnesse properties file.");
      }
      try {
        slimServerVersionMessage = reader.readLine();
      } catch (SocketTimeoutException e) {
        throw new SlimError(
            "Timeout while reading slim header from client. Check that you are connecting to the right port and that the slim client is running. You can increase the timeout limit by setting 'slim.timeout' in the fitnesse properties file.");
      }
      LOG.finest("Read Slim Header: >" + slimServerVersionMessage + "<");
    }
    try {
      slimServerVersion = Double.parseDouble(slimServerVersionMessage.replace(SlimVersion.SLIM_HEADER, ""));
    } catch (Exception e) {
      slimServerVersion = NO_SLIM_SERVER_CONNECTION_FLAG;
      throw new SlimError("Got invalid slim version from Client. Read the following: " + slimServerVersionMessage);
    }
    LOG.fine("Got Slim Header: " + slimServerVersionMessage + ", and Version " + slimServerVersion);
  }


  public double getServerVersion() {
    return slimServerVersion;
  }

  public boolean isConnected() {
    return slimServerVersionMessage != null && slimServerVersionMessage.startsWith(SlimVersion.SLIM_HEADER);
  }

  @Override
  public Map invokeAndGetResponse(List statements) throws SlimCommunicationException {
    if (statements.isEmpty())
      return Collections.emptyMap();
    String instructions = SlimSerializer.serialize(new SlimListBuilder(slimServerVersion).toList(statements));
    String results;
    try {
      SlimStreamReader.sendSlimMessage(writer, instructions);
      results = reader.getSlimMessage();
    } catch (IOException e) {
      throw new SlimCommunicationException("Could not send/receive data with SUT", e);
    }
    List resultList = SlimDeserializer.deserialize(results);
    return resultToMap(resultList);
  }

  @Override
  public void bye() throws IOException {
    SlimStreamReader.sendSlimMessage(writer, SlimVersion.BYEMESSAGE);
    slimRunner.join();
    kill();
  }

  public static Map resultToMap(List slimResults) {
    Map map = new HashMap<>();
    for (Object aResult : slimResults) {
      @SuppressWarnings("unchecked")
      List resultList = (List) aResult;
      map.put((String) resultList.get(0), resultList.get(1));
    }
    return map;
  }
}