@Override
  public Object execute(Object obj) {
    List<Object> params = (List<Object>) obj;

    // param 0: groupId
    groupId = ((String) params.get(0));

    // param 1: to indicate if all Servers will run in a single computer
    // or they will run Servers hosted in different computers (or more than one
    // Server in a single computer but this computer having the same internal and external IP
    // address)
    // * true: all Server run in a single computer
    // * false: Servers running in different computers (or more than one Server in a single computer
    // but
    // 			this computer having the same internal and external IP address)
    SimulationData.getInstance().setLocalExecution(((String) params.get(1)).equals("localMode"));

    try {
      users = (List<String>) params.get(2);
      operations = (List<Operation>) Serializer.deserialize((byte[]) params.get(3));
    } catch (ClassNotFoundException | IOException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    return null;
  }
  public void run() {
    //
    // Configuration of participating servers
    //

    // participating hosts
    List<Object> participants = new Vector<Object>();
    Hosts hosts = new Hosts();

    // receives info of participating servers
    // sends initial values for the algorithm and simulation
    Socket clientSocket = null;
    ObjectInputStream in = null;
    ObjectOutputStream out = null;

    final List<String> params = experimentData.getParams();
    System.out.println("TestServerExperimentManager -- params: " + params);

    int numNodes = experimentData.getNumNodes();
    String phase = (String) params.get(13);

    // in case of running together student's implementation and teacher's implementation,
    // check if the number of student's implementation is NUM_STUDENT_SERVERS
    String groupId = params.get(0);
    if (instantiateServers) {
      if (numNodes != NUM_STUDENT_SERVERS) {
        FileWriter outputStream = null;
        String result =
            "Error. The number of servers running the student's implementation should be: "
                + NUM_STUDENT_SERVERS;
        if (logResults) {
          File file = new File(path, groupId);
          try {
            //				outputStream = new FileWriter(results.get(0).getGroupId(),true);
            outputStream = new FileWriter(file, true);
            DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
            outputStream.append(
                groupId
                    + '\t'
                    + (dateFormat.format(new java.util.Date())).toString()
                    + '\t'
                    + result
                    + '\n');
            outputStream.close();
          } catch (IOException e) {
            e.printStackTrace();
          } finally {
            if (outputStream != null) {
              try {
                outputStream.close();
              } catch (IOException e) {
                e.printStackTrace();
              }
            }
          }
        }
        System.out.println("\n\n");
        System.out.println("*********** " + result);
        System.out.println("\n\n");
        return;
      }
      numNodes += NUM_TEACHER_SERVERS;

      // Execute script that runs NUM_TEACHER_SERVERS
      try {
        Runtime.getRuntime()
            .exec(
                "./startServers.sh "
                    + NUM_TEACHER_SERVERS
                    + " -p "
                    + testServerPort
                    + " -g "
                    + params.get(0));
        //
        //	Runtime.getRuntime().exec("/home/marques/eclipseProjects/SD/2013t-SD/2013t/scripts/startServers.sh "+NUM_TEACHER_SERVERS+" -p "+20000);
      } catch (IOException e1) {
        e1.printStackTrace();
      }
    }

    // send params to student's implementation and receive its id
    for (int i = 0; i < numNodes; i++) {
      try {
        serverSocket.setSoTimeout(
            45000); // sets a timeout. A read() call on the InputStream associated with this Socket
                    // will block for only this amount of time (milliseconds)
        clientSocket = serverSocket.accept();
        out = new ObjectOutputStream(clientSocket.getOutputStream());
        in = new ObjectInputStream(clientSocket.getInputStream());

        // send initialization parameters
        out.writeObject(params);

        // obtain the address of the remote participant server
        // ** method Serializer.serialize() is used to serialize node information
        // ** used to maintain compatibility with LSim, that requires a serialization
        Host host = (Host) in.readObject();
        participants.add(Serializer.serialize(host));
        hosts.add(host);

        in.close();
        out.close();
        clientSocket.close();
      } catch (SocketTimeoutException acceptException) {
        System.out.println(
            "Less than " + numNodes + " Serveres asked the initialization parameters");
        if (logResults) {
          File file = new File(path, experimentData.getGroupId());
          try {
            //				outputStream = new FileWriter(results.get(0).getGroupId()+".data",true);
            FileWriter outputStream = new FileWriter(file, true);
            DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
            outputStream.append(
                experimentData.getGroupId()
                    + '\t'
                    + (dateFormat.format(new java.util.Date())).toString()
                    + "\tLess than "
                    + numNodes
                    + " Serveres asked the initialization parameters"
                    + '\n');
            outputStream.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
        System.exit(50);
      } catch (IOException e) {
        System.err.println("TestServerExperimentManager -- Accept failed.");
        e.printStackTrace();
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      }
    }

    // Calculate the number of required results
    int numRequiredResults = ((numNodes * experimentData.getPercentageRequiredResults()) / 100 + 1);
    if (experimentData.getPercentageRequiredResults() == 100) {
      numRequiredResults = numNodes;
    }

    // sends the list of participating servers to servers
    for (int i = 0; i < numNodes; i++) {
      try {
        serverSocket.setSoTimeout(
            45000); // sets a timeout. A read() call on the InputStream associated with this Socket
                    // will block for only this amount of time (milliseconds)
        clientSocket = serverSocket.accept();
        out = new ObjectOutputStream(clientSocket.getOutputStream());
        out.writeObject(participants);
        out.close();
        clientSocket.close();
      } catch (SocketTimeoutException acceptException) {
        System.out.println("Less than " + numNodes + " Servers asked the list of participants");
        if (logResults) {
          File file = new File(path, experimentData.getGroupId());
          try {
            //				outputStream = new FileWriter(results.get(0).getGroupId()+".data",true);
            FileWriter outputStream = new FileWriter(file, true);
            DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
            outputStream.append(
                experimentData.getGroupId()
                    + '\t'
                    + (dateFormat.format(new java.util.Date())).toString()
                    + "\tLess than "
                    + numNodes
                    + " Serveres asked the list of participants"
                    + '\n');
            outputStream.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
        System.exit(50);
      } catch (IOException e) {
        System.err.println("Accept failed.");
        e.printStackTrace();
      }
    }

    // ************
    // ** Activity Generation
    // ************

    // Recipes
    Recipes recipesSent = new Recipes(); // contains all recipes issued and sent to a RaftCluster
    Recipes recipes = new Recipes(); // contains all recipes committed by the raft cluster

    // Create and run clients
    String recipeSurviveTitle = Math.abs(new Random().nextInt(10000)) + "-";
    String recipeDeadTitle = Math.abs(new Random().nextInt(10000)) + "-";
    Clients clients = new Clients();
    clients.setDataAndRunClients(
        recipesSent, recipes, params, hosts, recipeSurviveTitle, recipeDeadTitle);

    // ************
    // ** Results
    // ************

    // receives results from nodes
    List<ServerResult> finalResults = new Vector<ServerResult>();
    boolean end = false;
    HashMap<Integer, List<ServerResult>> allResults = new HashMap<Integer, List<ServerResult>>();

    try {
      serverSocket.setSoTimeout(
          900000); // sets a timeout. A read() call on the InputStream associated with this Socket
                   // will block for only this amount of time (milliseconds)
      do {
        clientSocket = serverSocket.accept();
        in = new ObjectInputStream(clientSocket.getInputStream());
        try {
          ResultBase result = (ResultBase) in.readObject();
          switch (result.type()) {
            case PARTIAL:
              Integer iteration = ((PartialResult) result).getIteration();
              List<ServerResult> results = null;
              if (allResults.containsKey(iteration)) {
                results = allResults.get(iteration);
              } else {
                results = new Vector<ServerResult>();
              }
              results.add(result.getServerResult());
              allResults.put(iteration, results);
              System.out.println(
                  "##### [iteration: "
                      + iteration
                      + "] partial result from server: "
                      + result.getServerResult().getHostId());
              break;
            case FINAL:
              finalResults.add(result.getServerResult());
              System.out.println(
                  "##### Final result from server: " + result.getServerResult().getHostId());
              if (finalResults.size() == numNodes) {
                //						if (finalResults.size() == numRequiredResults){
                end = true;
              }
              break;
          }
        } catch (ClassNotFoundException e) {
          e.printStackTrace();
        }
        in.close();
        clientSocket.close();
        serverSocket.setSoTimeout(
            45000); // sets a timeout. A read() call on the InputStream associated with this Socket
                    // will block for only this amount of time (milliseconds)
      } while (!end);
    } catch (SocketTimeoutException acceptException) {
      System.out.println("*********** Accept timeout");
    } catch (IOException e) {
      e.printStackTrace();
    }

    if (finalResults.size() < numRequiredResults) {
      String result =
          "Unable to evaluate results due to: Not enough Servers where connected at the moment of finishing the Activity Simulation phase."
              + "\tRecieved Results: "
              + finalResults.size()
              + "\tnumRequiredResults: "
              + numRequiredResults;
      if (logResults) {
        File file = new File(path, groupId);
        FileWriter outputStream = null;
        try {
          //				outputStream = new FileWriter(results.get(0).getGroupId()+".data",true);
          outputStream = new FileWriter(file, true);
          DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
          outputStream.append(
              groupId
                  + '\t'
                  + (dateFormat.format(new java.util.Date())).toString()
                  + '\t'
                  + result
                  + '\n');
          outputStream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      return;
      //			System.exit(30);
    }

    if (instantiateServers && !resultsFromTwoDifferentServers(finalResults)) {
      String result =
          "Unable to evaluate results due to: Only results from student's server. Evalution requires receiving results from teacher's Server. Check config.properties of Teacher's implementation to be sure that groupId field is the teacher's Id";
      if (logResults) {
        File file = new File(path, groupId);
        FileWriter outputStream = null;
        try {
          //				outputStream = new FileWriter(results.get(0).getGroupId()+".data",true);
          outputStream = new FileWriter(file, true);
          DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
          outputStream.append(
              groupId
                  + '\t'
                  + (dateFormat.format(new java.util.Date())).toString()
                  + '\t'
                  + result
                  + '\n');
          outputStream.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      System.err.println(">>>>>>>>>>>>>>>>>>>>>>> " + result);
      return;
    }

    try {
      serverSocket.close();
    } catch (IOException e) {
      e.printStackTrace();
    }

    System.out.println("\n\n");
    System.out.println("================================================");
    System.out.println("END OF EVALUATION");
    System.out.println("\n");
    System.out.println("RESULTS");
    System.out.println("=======");

    // evaluate final results
    boolean equal = false;

    System.out.println(
        "##### [" + finalResults.get(0).getHostId() + "] Result:\n " + finalResults.get(0));

    FileWriter outputStream = null;
    if (logResults) {
      File file = new File(path, groupId + ".data");
      try {
        //				outputStream = new FileWriter(results.get(0).getGroupId()+".data",true);
        outputStream = new FileWriter(file, true);
        DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        outputStream.append(
            "\n##### "
                + finalResults.get(0).getGroupId()
                + '\t'
                + (dateFormat.format(new java.util.Date())).toString());
        outputStream.append(
            "\n----- [" + finalResults.get(0).getHostId() + "] Result:\n " + finalResults.get(0));
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
    equal = true;
    for (int i = 1; i < finalResults.size() && equal; i++) {
      equal = equal && finalResults.get(0).equals(finalResults.get(i));
      if (!equal) {
        System.out.println(
            "##### [" + finalResults.get(i).getHostId() + "] Result:\n " + finalResults.get(i));
        if (logResults) {
          try {
            DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
            outputStream.append(
                "\n##### "
                    + finalResults.get(i).getGroupId()
                    + '\t'
                    + (dateFormat.format(new java.util.Date())).toString());
            outputStream.append(
                "\n----- ["
                    + finalResults.get(i).getHostId()
                    + "] Result:\n "
                    + finalResults.get(i));
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
    }

    // calculate in which iteration nodes converged
    boolean converged = false;
    int convergenceIteration = -1;
    for (int it = 0; allResults.containsKey(Integer.valueOf(it)) && !converged; it++) {
      List<ServerResult> results = allResults.get(Integer.valueOf(it));
      converged = (allResults.size() >= finalResults.size());
      for (int i = 1; i < results.size() && converged; i++) {
        converged = converged && results.get(0).equals(results.get(i));
      }
      if (converged) {
        convergenceIteration = it;
      }
    }

    if (phase.equals("4.1")) {
      int deadCount = 0;
      int liveCount = 0;
      for (ServerResult serverResult : finalResults) {
        undoLastAdds(serverResult);
        deadCount = deadCount + countRecipes(serverResult, recipeDeadTitle);
        liveCount = liveCount + countRecipes(serverResult, recipeSurviveTitle);
      }
      equal = equal && (deadCount == 0) && (liveCount > 0);
      //			System.out.println("\n\t" +
      //					"Having "+deadCount+" dead results <<< "+
      //					(finalResults.size() * clients.getCount()) + " dead-recipes:" + recipeDeadTitle + "*");
      //			result += "\n\t" +
      //					"Having "+deadCount+" dead results <<< "+
      //					(finalResults.size() * clients.getCount()) + " dead-recipes:" + recipeDeadTitle + "*";
      if (logResults) {
        try {
          DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
          outputStream.append(
              "\n\n##### Phase 4.1"
                  //							+ finalResults.get(i).getGroupId()
                  + '\t'
                  + (dateFormat.format(new java.util.Date())).toString());
          if (deadCount == 0 && liveCount > 0) {
            outputStream.append('\n' + "Phase 4.1 is correct (live: " + liveCount + ")");
          } else {
            outputStream.append(
                '\n'
                    + "ERROR in Phase 4.1: Raft re-executed an operation multiple times"
                    + '\n'
                    + "i.e. it has received:"
                    + '\n'
                    + '\t'
                    + "(a) AddOperation(recipeN, timestampX)"
                    + '\n'
                    + '\t'
                    + "(b) RemoveOperation(recipeN.title,timestampY)"
                    + '\n'
                    + '\t'
                    + "(c) AddOperation(recipeN, timestampX)"
                    + '\n'
                    + "It shouldn't have executed the second AddOperation(recipeN, timestampX)");
          }
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

    if (logResults) {
      try {
        outputStream.append("\n================================================\n");
      } catch (IOException e) {
        e.printStackTrace();
      } finally {
        if (outputStream != null) {
          try {
            outputStream.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
    }

    // write final result
    System.out.println("\n\n");
    String result = "phase " + phase;
    if (equal) {
      result += '\t' + "Correct";
      if (convergenceIteration == -1) {
        //				result += '\n' + '\t' + "Nodes converged at the last iteration ";
      } else {
        //				result += '\n' + '\t' + "Nodes converged at the iteration " + convergenceIteration;
      }
    } else {
      result += '\t' + "Servers DON'T have coherent data";
    }

    System.out.println(result);

    System.out.println("\n\n");
    System.out.println("================================================");
    System.out.println("\n\n");

    if (logResults) {
      File file = new File(path, groupId);
      try {
        //				outputStream = new FileWriter(results.get(0).getGroupId(),true);
        outputStream = new FileWriter(file, true);
        DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        outputStream.append(
            groupId
                + '\t'
                + (dateFormat.format(new java.util.Date())).toString()
                + '\t'
                + result
                + '\n');
        outputStream.close();
      } catch (IOException e) {
        e.printStackTrace();
      } finally {
        if (outputStream != null) {
          try {
            outputStream.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
        }
      }
    }

    System.out.println("\n\n");
    System.out.println("*********** num received results: " + finalResults.size());
    System.out.println("*********** % received results: " + (finalResults.size() * 100) / numNodes);
    System.out.println("*********** minimal required number of results: " + numRequiredResults);
    System.out.println("\n\n");

    //		if (equal){
    //			System.exit(10);
    //		} else{
    //			System.exit(20);
    //		}
  }