package org.radargun;

import java.io.IOException;
import java.util.Map;

import org.radargun.config.Cluster;
import org.radargun.config.Configuration;
import org.radargun.config.InitHelper;
import org.radargun.config.PropertyHelper;
import org.radargun.config.Scenario;
import org.radargun.logging.Log;
import org.radargun.logging.LogFactory;
import org.radargun.reporting.Timeline;
import org.radargun.stages.ScenarioCleanupStage;
import org.radargun.state.SlaveState;
import org.radargun.traits.TraitHelper;
import org.radargun.utils.TimeService;

/**
 * Base class for both standalone slave and slave integrated in master node (local cluster).
 *
 * @author Radim Vansa &lt;rvansa@redhat.com&gt;
 */
public abstract class SlaveBase {
   protected final Log log = LogFactory.getLog(getClass());

   protected SlaveState state = new SlaveState();
   protected Configuration configuration;
   protected Cluster cluster;
   protected Scenario scenario;

   protected void scenarioLoop() throws IOException {
      Cluster.Group group = cluster.getGroup(state.getSlaveIndex());
      Configuration.Setup setup = configuration.getSetup(group.name);
      state.setCluster(cluster);
      state.setPlugin(setup.plugin);
      state.setService(setup.service);
      state.setTimeline(new Timeline(state.getSlaveIndex()));
      Map<String, String> extras = getCurrentExtras(configuration, cluster);
      ServiceHelper.setServiceContext(setup.plugin, configuration.name, state.getSlaveIndex());
      Object service = ServiceHelper.createService(setup.plugin, setup.service, setup.getProperties(), extras);
      Map<Class<?>, Object> traits = null;
      try {
         log.info("Service is " + service.getClass().getSimpleName() + PropertyHelper.toString(service));
         traits = TraitHelper.retrieve(service);
         state.setTraits(traits);
         for (; ; ) {
            int stageId = getNextStageId();
            Map<String, Object> masterData = getNextMasterData();
            for (Map.Entry<String, Object> entry : masterData.entrySet()) {
               state.put(entry.getKey(), entry.getValue());
            }
            log.trace("Received stage ID " + stageId);
            DistStage stage = (DistStage) scenario.getStage(stageId, state, extras, null);
            if (stage instanceof ScenarioCleanupStage) {
               // this is always the last stage and is ran in main thread (not sc-main)
               break;
            }
            TraitHelper.InjectResult result = null;
            DistStageAck response;
            Exception initException = null;
            try {
               result = TraitHelper.inject(stage, traits);
               InitHelper.init(stage);
               stage.initOnSlave(state);
            } catch (Exception e) {
               log.error("Stage initialization has failed", e);
               initException = e;
            }
            if (initException != null) {
               response = new DistStageAck(state).error("Stage initialization has failed", initException);
            } else if (!stage.shouldExecute()) {
               log.info("Stage should not be executed");
               response = new DistStageAck(state);
            } else if (result == TraitHelper.InjectResult.SKIP) {
               log.info("Stage was skipped because it was missing some traits");
               response = new DistStageAck(state);
            } else if (result == TraitHelper.InjectResult.FAILURE) {
               String message = "The stage was not executed because it missed some mandatory traits.";
               log.error(message);
               response = new DistStageAck(state).error(message, null);
            } else {
               String stageName = stage.getName();
               log.info("Starting stage " + (log.isDebugEnabled() ? stage.toString() : stageName));
               long start = TimeService.currentTimeMillis();
               long end;
               try {
                  response = stage.executeOnSlave();
                  end = TimeService.currentTimeMillis();
                  if (response == null) {
                     response = new DistStageAck(state).error("Stage returned null response", null);
                  }
                  log.info("Finished stage " + stageName);
                  response.setDuration(end - start);
               } catch (Exception e) {
                  end = TimeService.currentTimeMillis();
                  log.error("Stage execution has failed", e);
                  response = new DistStageAck(state).error("Stage execution has failed", e);
               } finally {
                  InitHelper.destroy(stage);
               }
               state.getTimeline().addEvent(Stage.STAGE, new Timeline.IntervalEvent(start, stageName, end - start));
            }
            sendResponse(response);
         }
      } finally {
         if (traits != null) {
            for (Object trait : traits.values()) {
               InitHelper.destroy(trait);
            }
         }
         InitHelper.destroy(service);
      }
   }

   protected abstract int getNextStageId() throws IOException;

   protected abstract Map<String, Object> getNextMasterData() throws IOException;

   protected abstract void sendResponse(DistStageAck response) throws IOException;

   protected void runCleanup() throws IOException {
      DistStageAck response = null;
      try {
         Map<String, String> extras = getCurrentExtras(configuration, cluster);
         ScenarioCleanupStage stage = (ScenarioCleanupStage) scenario.getStage(scenario.getStageCount() - 1, state, extras, null);
         InitHelper.init(stage);
         stage.initOnSlave(state);
         log.info("Starting stage " + (log.isDebugEnabled() ? stage.toString() : stage.getName()));
         response = stage.executeOnSlave();
      } catch (Exception e) {
         log.error("Stage execution has failed", e);
         response = new DistStageAck(state).error("Stage execution has failed", e);
      } finally {
         if (response == null) {
            response = new DistStageAck(state).error("Stage returned null response", null);
         }
         sendResponse(response);
      }
   }

   protected abstract Map<String, String> getCurrentExtras(Configuration configuration, Cluster cluster);

   // In RadarGun 2.0, we had to run each service in new thread in order to prevent
   // classloader leaking through thread locals. This is not necessary anymore,
   // but we still do checks in ScenarioCleanupStage
   protected class ScenarioRunner extends Thread {
      protected ScenarioRunner() {
         super("sc-main");
      }

      @Override
      public void run() {
         try {
            scenarioLoop();
         } catch (IOException e) {
            log.error("Communication with master failed", e);
            e.printStackTrace();
            ShutDownHook.exit(127);
         } catch (Throwable t) {
            log.error("Unexpected error in scenario", t);
            t.printStackTrace();
            ShutDownHook.exit(127);
         }
      }
   }
}

@@ -1859,11 +1859,9 @@
 r (;
- ; 
+;
 ) %7B%0A
@@ -2794,32 +2794,58 @@
 log.error(%22Stage
+ '%22 + stage.getName() + %22'
  initialization 
@@ -3012,16 +3012,42 @@
 r(%22Stage
+ '%22 + stage.getName() + %22'
  initial
@@ -3066,16 +3066,37 @@
 failed%22,
+%0A                    
  initExc
@@ -3183,16 +3183,42 @@
 o(%22Stage
+ '%22 + stage.getName() + %22'
  should 
@@ -3379,24 +3379,50 @@
 .info(%22Stage
+ '%22 + stage.getName() + %22'
  was skipped
@@ -3622,16 +3622,63 @@
 he stage
+ '%22 + stage.getName()%0A                     + %22'
  was not
@@ -5756,32 +5756,47 @@
 nt() - 1, state,
+%0A              
  extras, null);%0A

Add stage name to failure messages to ease debugging