/**
   * @param xmlSpecification
   * @param userGroup
   * @return
   */
  public ValidationReport Validate(String xmlSpecification, String userGroup) {
    final String methodName = "Validate";
    Logfile.WriteCalled(logLevel, STR_ClassName, methodName);

    ValidationReport validationReport = null;

    try {
      if (this.labExperimentEngines == null) {
        throw new NullPointerException(STRERR_LabExperimentEngines);
      }

      /*
       * Pass to the each experiment engine in turn to validate until validation is successful.
       * It may be possible that the LabEquipment for a particular LabExperimentEngine may be offline.
       */
      for (int i = 0; i < this.labExperimentEngines.length; i++) {
        LabExperimentEngine labExperimentEngine = this.labExperimentEngines[i];
        if (labExperimentEngine == null) {
          throw new NullPointerException(String.format(STRERR_LabExperimentEngineUnitId_arg, 0));
        }

        /*
         * Check if the LabExperimentEngine is online before trying to use it to validate
         */
        LabStatus labStatus = labExperimentEngine.GetLabStatus();
        if (labStatus.isOnline() == false) {
          continue;
        }

        /*
         * Validate the specification
         */
        validationReport = labExperimentEngine.Validate(xmlSpecification);
        if (validationReport.isAccepted() == true) {
          break;
        }
      }
    } catch (Exception ex) {
      Logfile.WriteError(ex.toString());
      validationReport = new ValidationReport(ex.toString());
    }

    Logfile.WriteCompleted(
        logLevel,
        STR_ClassName,
        methodName,
        String.format(
            STRLOG_Accepted_arg,
            (validationReport != null) ? validationReport.isAccepted() : false));

    return validationReport;
  }
  /** @return */
  private int GetMinRemainingRuntime() {
    final String methodName = "GetMinRemainingRuntime";
    Logfile.WriteCalled(logLevel, STR_ClassName, methodName);

    int minRemainingRuntime = Integer.MAX_VALUE;

    try {
      if (this.labExperimentEngines == null) {
        throw new NullPointerException(STRERR_LabExperimentEngines);
      }

      /*
       * Get the remaining runtime for each experiment engine
       */
      for (int unitId = 0; unitId < this.labManagement.getFarmSize(); unitId++) {
        /*
         * Get the experiment engine
         */
        LabExperimentEngine labExperimentEngine = this.labExperimentEngines[unitId];
        if (labExperimentEngine == null) {
          throw new NullPointerException(
              String.format(STRERR_LabExperimentEngineUnitId_arg, unitId));
        }

        /*
         * Check to see if the experiment engine is online
         */
        LabStatus labStatus = labExperimentEngine.GetLabStatus();
        if (labStatus.isOnline() == true) {
          /*
           * Get the remaining runtime for this engine and check if this is a smaller value
           */
          int remainingRuntime = labExperimentEngine.GetRemainingRuntime();
          if (remainingRuntime < minRemainingRuntime) {
            minRemainingRuntime = remainingRuntime;
          }
        }
      }
    } catch (Exception ex) {
      Logfile.WriteError(ex.toString());
    }

    Logfile.WriteCompleted(logLevel, STR_ClassName, methodName);

    return minRemainingRuntime;
  }
  @Override
  public void run() {
    final String methodName = "run";
    Logfile.WriteCalled(logLevel, STR_ClassName, methodName);

    /*
     * Initialise state machine
     */
    States lastState = States.Done;
    States thisState = States.Init;
    int nextUnit = this.labManagement.getFarmSize() - 1;
    this.running = true;

    /*
     * Allow other threads to check the state of this thread
     */
    Delay.MilliSeconds(500);

    /*
     * State machine loop
     */
    try {
      boolean success;

      while (thisState != States.Done) {
        /*
         * Display message on each state change
         */
        if (thisState != lastState) {
          String logMessage =
              String.format(STRLOG_StateChange_arg2, lastState.toString(), thisState.toString());
          if (debugTrace == true) {
            System.out.println(logMessage);
          }
          //                    Logfile.Write(logLevel, logMessage);

          lastState = thisState;
        }

        switch (thisState) {
          case Init:
            /*
             * Update lab status
             */
            this.online = true;
            this.labStatusMessage = StatusCodes.Ready.toString();

            /*
             * Revert any 'Running' experiments back to 'Waiting' so that they can be run again
             */
            ArrayList<ExperimentQueueInfo> experimentQueueInfoList =
                this.labManagement.getExperimentQueueDB().RetrieveByStatusCode(StatusCodes.Running);
            if (experimentQueueInfoList != null) {
              for (ExperimentQueueInfo experimentQueueInfo : experimentQueueInfoList) {
                success =
                    this.labManagement
                        .getExperimentQueueDB()
                        .UpdateStatus(experimentQueueInfo.getId(), StatusCodes.Waiting);

                /*
                 * Delete the database statistics entry for this experiment
                 */
                ExperimentStatisticsInfo experimentStatisticInfo =
                    this.labManagement
                        .getExperimentStatisticsDB()
                        .RetrieveByExperimentId(
                            experimentQueueInfo.getExperimentId(), experimentQueueInfo.getSbName());
                if (experimentStatisticInfo != null) {
                  this.labManagement
                      .getExperimentStatisticsDB()
                      .Delete(experimentStatisticInfo.getId());
                }

                /*
                 * Delete the database results entry for this experiment
                 */
                ExperimentResultInfo experimentResultInfo =
                    this.labManagement
                        .getExperimentResultsDB()
                        .RetrieveByExperimentId(
                            experimentQueueInfo.getExperimentId(), experimentQueueInfo.getSbName());
                if (experimentResultInfo != null) {
                  this.labManagement.getExperimentResultsDB().Delete(experimentResultInfo.getId());
                }

                String logMessage =
                    String.format(
                        STRLOG_RevertToWaiting_arg,
                        experimentQueueInfo.getExperimentId(),
                        experimentQueueInfo.getSbName(),
                        success);
                Logfile.Write(logMessage);
              }
            }

            /*
             * Check if any experiments have not notified their ServiceBroker
             */
            thisState = States.CheckNotified;
            break;

          case Idle:
            /*
             * Wait for an experiment to be submitted or timeout after a certain time. In either case, check
             * the experiment queue. Maybe an experiment submission signal got missed and it didn't get seen
             * here. It has happened before.
             */
            if (this.labManagement.getSignalSubmitted().Wait(INT_DelayCheckQueueSeconds * 1000)
                == true) {
              /*
               * Check if shutting down
               */
              if (this.stopRunning == true) {
                thisState = States.StopRunning;
                break;
              }

              /*
               * An experiment has been submitted, go check the queue
               */
              this.labManagement.getSignalSubmitted().Reset();
              thisState = States.CheckQueue;
              break;
            }

            /*
             * Timed out, go check some other things
             */
            thisState = States.Maintenance;
            break;

          case CheckQueue:
            /*
             * Check the queue to see if there are any experiments waiting
             */
            if (this.labManagement.getExperimentQueueDB().GetCountWaiting() > 0) {
              thisState = States.StartEngine;
              break;
            }

            thisState = States.Idle;
            break;

          case StartEngine:
            /*
             * Find the available experiment engine to run a waiting experiment
             */
            boolean foundAvailable = false;
            for (int i = 0; i < this.labManagement.getFarmSize(); i++) {
              /*
               * Determine the next experiment engine for running the experiment
               */
              if (++nextUnit == this.labManagement.getFarmSize()) {
                nextUnit = 0;
              }
              if (debugTrace == true) {
                System.out.println(String.format("[nextUnit: %s]", nextUnit));
              }

              /*
               * Get the experiment engine and check to see if it is online
               */
              LabExperimentEngine labExperimentEngine = this.labExperimentEngines[nextUnit];
              LabStatus labStatus = labExperimentEngine.GetLabStatus();
              if (labStatus.isOnline() == false) {
                /*
                 * This one is not online
                 */
                if (debugTrace == true) {
                  System.out.println(String.format("[nextUnit: %s is Offline]", nextUnit));
                }
                continue;
              }

              /*
               * Determine if this experiment engine is currently running
               */
              if (labExperimentEngine.isRunning() == false) {
                /*
                 * Not running, try starting it
                 */
                if (labExperimentEngine.Start() == true) {
                  /*
                   * The available engine has been started, wait a bit before checking the queue
                   */
                  foundAvailable = true;
                  Delay.MilliSeconds(1000);
                  break;
                }
              }
            }

            /*
             * Check if there are any engines available
             */
            if (foundAvailable == false) {
              /*
               * No engines available to start running
               */
              thisState = States.Idle;
              break;
            }

            thisState = States.CheckNotified;
            break;

          case CheckNotified:
            /*
             * Check if any experiments have not notified their ServiceBroker
             */
            success = true;
            ArrayList<ExperimentResultInfo> allNotNotified =
                this.labManagement.getExperimentResultsDB().RetrieveAllNotNotified();
            if (allNotNotified != null) {
              Iterator iterator = allNotNotified.iterator();
              while (iterator.hasNext() && success == true) {
                ExperimentResultInfo experimentResultInfo = (ExperimentResultInfo) iterator.next();
                success =
                    this.labExperimentEngines[0].NotifyServiceBroker(
                        experimentResultInfo.getExperimentId(), experimentResultInfo.getSbName());
              }
            }

            thisState = States.CheckQueue;
            break;

          case Maintenance:
            thisState = States.Idle;
            break;

          case StopRunning:
            thisState = States.Done;
            break;
        }
      }
    } catch (Exception ex) {
      Logfile.WriteError(ex.toString());
    }

    /*
     * Thread is no longer running
     */
    this.running = false;

    Logfile.WriteCompleted(logLevel, STR_ClassName, methodName);
  }
  /** @return LabStatus */
  public LabStatus GetLabStatus(boolean verbose) {
    final String methodName = "GetLabStatus";
    Logfile.WriteCalled(logLevel, STR_ClassName, methodName);

    LabStatus labStatus = new LabStatus(false, "");

    try {
      if (this.labExperimentEngines == null) {
        throw new NullPointerException(STRERR_LabExperimentEngines);
      }

      /*
       * Check lab status of each experiment engine
       */
      for (int unitId = 0; unitId < this.labManagement.getFarmSize(); unitId++) {
        LabExperimentEngine labExperimentEngine = this.labExperimentEngines[unitId];
        if (labExperimentEngine == null) {
          throw new NullPointerException(
              String.format(STRERR_LabExperimentEngineUnitId_arg, unitId));
        }

        /*
         * Keep a tally, add the LabStatusMessage only if the engine is online
         */
        LabStatus engineLabStatus = labExperimentEngine.GetLabStatus();
        labStatus.setOnline(labStatus.isOnline() || engineLabStatus.isOnline());
        String message;
        if (engineLabStatus.isOnline() == true) {

          /*
           * Check if the engine is currently running an experiment
           */
          int experimentId = labExperimentEngine.getExperimentId();
          if (experimentId > 0) {
            /*
             * It is, so include the experiment Id in the lab status message
             */
            message =
                String.format(
                    STRLOG_UnitIdExperimentIdLabStatusMessage_arg2,
                    unitId,
                    experimentId,
                    engineLabStatus.getLabStatusMessage());
          } else {
            message =
                String.format(
                    STRLOG_UnitIdLabStatusMessage_arg2,
                    unitId,
                    engineLabStatus.getLabStatusMessage());
          }
        } else {
          message =
              (verbose == true)
                  ? String.format(
                      STRLOG_UnitIdLabStatusMessage_arg2,
                      unitId,
                      engineLabStatus.getLabStatusMessage())
                  : "";
        }
        labStatus.setLabStatusMessage(labStatus.getLabStatusMessage() + message);
      }
    } catch (Exception ex) {
      Logfile.WriteError(ex.toString());
      labStatus.setLabStatusMessage(ex.toString());
    }

    Logfile.WriteCompleted(logLevel, STR_ClassName, methodName);

    return labStatus;
  }