/**
   * @param experimentId
   * @param sbName
   * @param xmlSpecification
   * @param userGroup
   * @param priorityHint
   * @return
   */
  public synchronized SubmissionReport Submit(
      int experimentId,
      String sbName,
      String xmlSpecification,
      String userGroup,
      int priorityHint) {
    final String methodName = "Submit";
    Logfile.WriteCalled(
        logLevel,
        STR_ClassName,
        methodName,
        String.format(STRLOG_ExperimentIdSbName_arg2, experimentId, sbName));

    SubmissionReport submissionReport = new SubmissionReport(experimentId);

    /*
     * Validate the experiment specification before submitting
     */
    ValidationReport validationReport = this.Validate(xmlSpecification, userGroup);
    if (validationReport.isAccepted() == true) {
      /*
       * Specification is valid, create an instance of the experiment
       */
      LabExperimentInfo labExperimentInfo = new LabExperimentInfo(experimentId, sbName);
      labExperimentInfo.setXmlSpecification(xmlSpecification);
      labExperimentInfo.setUserGroup(userGroup);
      labExperimentInfo.setPriorityHint(priorityHint);
      labExperimentInfo.setEstimatedExecTime((int) validationReport.getEstRuntime());

      /*
       * Add the experiment to the queue
       */
      try {
        QueuedExperimentInfo queuedExperimentInfo =
            this.labManagement.getExperimentQueueDB().Enqueue(labExperimentInfo);
        if (queuedExperimentInfo == null) {
          throw new RuntimeException(STRERR_FailedToEnqueueExperiment);
        }

        /*
         * Update submission report current queue length and wait time
         */
        WaitEstimate waitEstimate = new WaitEstimate();
        waitEstimate.setEffectiveQueueLength(queuedExperimentInfo.getQueueLength());
        waitEstimate.setEstWait(queuedExperimentInfo.getWaitTime() + this.GetMinRemainingRuntime());
        submissionReport.setWaitEstimate(waitEstimate);

        /*
         * Update the statistics with revised wait estimate
         */
        queuedExperimentInfo.setWaitTime((int) waitEstimate.getEstWait());
        this.labManagement.getExperimentStatisticsDB().Submitted(queuedExperimentInfo);

        /*
         * Tell lab experiment manager thread that an experiment has been submitted
         */
        this.labManagement.getSignalSubmitted().Notify();
      } catch (Exception ex) {
        validationReport = new ValidationReport(ex.getMessage());
      }
    }

    submissionReport.setValidationReport(validationReport);

    Logfile.WriteCompleted(
        logLevel,
        STR_ClassName,
        methodName,
        String.format(STRLOG_Accepted_arg, submissionReport.getValidationReport().isAccepted()));

    return submissionReport;
  }
  /**
   * @param experimentId
   * @param sbName
   * @return
   */
  public synchronized LabExperimentStatus GetLabExperimentStatus(int experimentId, String sbName) {
    final String methodName = "GetLabExperimentStatus";
    Logfile.WriteCalled(
        Level.INFO,
        STR_ClassName,
        methodName,
        String.format(STRLOG_ExperimentIdSbName_arg2, experimentId, sbName));

    LabExperimentStatus labExperimentStatus = new LabExperimentStatus();

    try {
      /*
       * Check that parameters are valid
       */
      if (experimentId <= 0) {
        throw new RuntimeException(String.format(STRERR_InvalidExperimentId_arg, experimentId));
      }
      if (sbName == null) {
        throw new NullPointerException(STRERR_SbName);
      }

      /*
       * Get the status of the experiment from the queue
       */
      ExperimentQueueInfo experimentQueueInfo =
          this.labManagement.getExperimentQueueDB().RetrieveByExperimentId(experimentId, sbName);
      if (experimentQueueInfo == null) {
        throw new RuntimeException(
            String.format(STRERR_UnknownExperimentSbNameExperimentId_arg2, sbName, experimentId));
      }

      System.out.println(String.format("StatusCode: %s", experimentQueueInfo.getStatusCode()));

      /*
       * Experiment exists, check status
       */
      switch (experimentQueueInfo.getStatusCode()) {
        case Waiting:
          /*
           * Experiment is waiting on the queue, get the queue position and wait time
           */
          QueuedExperimentInfo queuedExperimentInfo =
              this.labManagement
                  .getExperimentQueueDB()
                  .GetQueuedExperimentInfo(experimentId, sbName);
          WaitEstimate waitEstimate = new WaitEstimate();
          waitEstimate.setEffectiveQueueLength(queuedExperimentInfo.getPosition());
          waitEstimate.setEstWait(
              queuedExperimentInfo.getWaitTime() + this.GetMinRemainingRuntime());

          System.out.println(
              String.format(
                  "WaitEstimate: QueueLength=%d  WaitTime=%f01",
                  waitEstimate.getEffectiveQueueLength(), waitEstimate.getEstWait()));

          /*
           * Set the experiment status and time it takes to run the experiment
           */
          ExperimentStatus experimentStatus =
              new ExperimentStatus(experimentQueueInfo.getStatusCode());
          experimentStatus.setEstRuntime(experimentQueueInfo.getEstimatedExecTime());
          experimentStatus.setEstRemainingRuntime(experimentQueueInfo.getEstimatedExecTime());
          experimentStatus.setWaitEstimate(waitEstimate);

          labExperimentStatus = new LabExperimentStatus(experimentStatus);

          Logfile.Write(
              logLevel,
              String.format(
                  STRLOG_ExperimentStatus_arg4,
                  waitEstimate.getEffectiveQueueLength(),
                  waitEstimate.getEstWait(),
                  experimentStatus.getEstRuntime(),
                  experimentStatus.getEstRemainingRuntime()));

          System.out.println(
              String.format(
                  STRLOG_ExperimentStatus_arg4,
                  waitEstimate.getEffectiveQueueLength(),
                  waitEstimate.getEstWait(),
                  experimentStatus.getEstRuntime(),
                  experimentStatus.getEstRemainingRuntime()));
          break;

        case Running:
          /*
           * Get the experiment status from the lab experiment engine
           */
          LabExperimentEngine labExperimentEngine =
              this.labExperimentEngines[experimentQueueInfo.getUnitId()];
          labExperimentStatus.setExperimentStatus(
              labExperimentEngine.GetExperimentStatus(experimentId, sbName));
          break;

        case Cancelled:
          /*
           * The experiment was cancelled while waiting on the queue
           */
          labExperimentStatus.setExperimentStatus(new ExperimentStatus(StatusCodes.Cancelled));
          break;

        default:
          /*
           * Experiment has completed, cancelled or failed so get the status from the experiment results
           */
          ResultReport resultReport =
              this.labManagement
                  .getExperimentResultsDB()
                  .RetrieveResultReport(experimentId, sbName);
          labExperimentStatus.setExperimentStatus(
              new ExperimentStatus(resultReport.getStatusCode()));
          break;
      }
    } catch (Exception ex) {
      Logfile.WriteError(ex.toString());
      labExperimentStatus.setExperimentStatus(new ExperimentStatus(StatusCodes.Unknown));
    }

    ExperimentStatus experimentStatus = labExperimentStatus.getExperimentStatus();

    Logfile.WriteCompleted(
        Level.INFO,
        STR_ClassName,
        methodName,
        String.format(
            STRLOG_ExperimentStatus_arg3,
            experimentStatus.getStatusCode(),
            experimentStatus.getEstRuntime(),
            experimentStatus.getEstRemainingRuntime()));

    return labExperimentStatus;
  }