/**
   * Finalizes a {@code Job} execution by a given context in which the job was performed and by the
   * exit status of the step. If the {@code Job} execution continues beyond the scope of the
   * command, the {@code Job.isAsyncJob()} should be set to {@code true}. If {@code
   * ExecutionMethod.AsStep} is defined, the current active step can end the running {@code Job} by
   * setting the {@ExecutionContext.shouldEndJob()} to {@code true}.
   *
   * @param executionContext The context of the execution which defines how the job should be ended
   * @param exitStatus Indicates if the execution described by the job ended successfully or not.
   */
  public static void endJob(ExecutionContext context, boolean exitStatus) {
    if (context == null) {
      return;
    }

    Job job = context.getJob();

    try {
      if (context.isMonitored()) {
        if (context.getExecutionMethod() == ExecutionMethod.AsJob && job != null) {
          if (context.shouldEndJob() || !(job.isAsyncJob() && exitStatus)) {
            context.setCompleted(true);
            endJob(exitStatus, job);
          }
        } else {
          Step step = context.getStep();
          if (context.getExecutionMethod() == ExecutionMethod.AsStep && step != null) {
            if (context.shouldEndJob()) {
              if (job == null) {
                job = JobRepositoryFactory.getJobRepository().getJob(step.getJobId());
              }

              if (job != null) {
                context.setCompleted(true);
                endJob(exitStatus, job);
              }
            }
          }
        }
      }
    } catch (Exception e) {
      log.error(e);
    }
  }
  /**
   * Creates {@code ExecutionContext} which defines the context for executing the finalizing step of
   * the job. If the step exists, it must be part of a job, therefore the {@code Job} entity is
   * being set as part of the context.
   *
   * @param stepId The unique identifier of the step. Must not be {@code null}.
   * @return The context for monitoring the finalizing step of the job, or {@code null} if no such
   *     step.
   */
  public static ExecutionContext createFinalizingContext(Guid stepId) {
    ExecutionContext context = null;
    try {
      Step step = JobRepositoryFactory.getJobRepository().getStep(stepId);
      if (step != null && step.getParentStepId() != null) {
        context = new ExecutionContext();
        Step executionStep =
            JobRepositoryFactory.getJobRepository().getStep(step.getParentStepId());

        // indicates if a step is monitored at Job level or as an inner step
        Guid parentStepId = executionStep.getParentStepId();
        if (parentStepId == null) {
          context.setExecutionMethod(ExecutionMethod.AsJob);
          context.setJob(JobRepositoryFactory.getJobRepository().getJobWithSteps(step.getJobId()));
        } else {
          context.setExecutionMethod(ExecutionMethod.AsStep);
          Step parentStep = JobRepositoryFactory.getJobRepository().getStep(parentStepId);
          parentStep.setSteps(
              DbFacade.getInstance().getStepDao().getStepsByParentStepId(parentStep.getId()));
          context.setStep(parentStep);
        }
        context.setMonitored(true);
      }
    } catch (Exception e) {
      log.error(e);
    }
    return context;
  }
 /**
  * Updates the step with the id in the external system in which the describe task runs.
  *
  * @param step The step which represents the external task
  * @param externalId The id of the task in the external system
  * @param systemType The type of the system
  */
 public static void updateStepExternalId(
     Step step, Guid externalId, ExternalSystemType systemType) {
   if (step != null) {
     step.getExternalSystem().setId(externalId);
     step.getExternalSystem().setType(systemType);
     try {
       JobRepositoryFactory.getJobRepository().updateStep(step);
     } catch (Exception e) {
       log.errorFormat(
           "Failed to save step {0}, {1} for system-type {2} with id {3}",
           step.getId(), step.getStepType().name(), systemType.name(), externalId, e);
     }
   }
 }
  /**
   * Finalizes a {@code Step} execution which represents a VDSM task. In case of a failure status,
   * the job will not be marked as failed at this stage, but via executing the {@code
   * CommandBase.endAction} with the proper status by {@code the AsyncTaskManager}.
   *
   * @param stepId A unique identifier of the step to finalize.
   * @param exitStatus The status which the step should be ended with.
   */
  public static void endTaskStep(Guid stepId, JobExecutionStatus exitStatus) {
    try {
      if (stepId != null) {
        Step step = JobRepositoryFactory.getJobRepository().getStep(stepId);

        if (step != null) {
          step.markStepEnded(exitStatus);
          JobRepositoryFactory.getJobRepository().updateStep(step);
        }
      }
    } catch (Exception e) {
      log.errorFormat("Failed to terminate step {0} with status {1}", stepId, exitStatus, e);
    }
  }
  /**
   * Finalizes a {@code Step} execution by a given context in which the step was performed and by
   * the exit status of the step.
   *
   * @param context The context in which the {@code Step} was executed.
   * @param step The step to finalize.
   * @param exitStatus Indicates if the execution described by the step ended successfully or not.
   */
  public static void endStep(ExecutionContext context, Step step, boolean exitStatus) {
    if (context == null) {
      return;
    }
    if (context.isMonitored()) {
      Job job = context.getJob();
      try {
        if (step != null) {
          step.markStepEnded(exitStatus);
          JobRepositoryFactory.getJobRepository().updateStep(step);
        }

        if (context.getExecutionMethod() == ExecutionMethod.AsJob && job != null && !exitStatus) {
          // step failure will cause the job to be marked as failed
          context.setCompleted(true);
          job.markJobEnded(false);
          JobRepositoryFactory.getJobRepository().updateCompletedJobAndSteps(job);
        } else {
          Step parentStep = context.getStep();
          if (context.getExecutionMethod() == ExecutionMethod.AsStep && parentStep != null) {
            context.setCompleted(true);
            if (!exitStatus) {
              job.markJobEnded(false);
              JobRepositoryFactory.getJobRepository().updateCompletedJobAndSteps(job);
            }
          }
        }
      } catch (Exception e) {
        log.error(e);
      }
    }
  }
  private static Step addSubStep(Step parentStep, StepEnum stepName, String description) {
    Step step = null;

    if (parentStep != null) {
      if (description == null) {
        description = ExecutionMessageDirector.getInstance().getStepMessage(stepName);
      }
      step = parentStep.addStep(stepName, description);

      try {
        JobRepositoryFactory.getJobRepository().saveStep(step);
      } catch (Exception e) {
        log.errorFormat(
            "Failed to save new step {0} for step {1}, {2}.",
            stepName.name(), parentStep.getId(), parentStep.getStepType().name(), e);
        parentStep.getSteps().remove(step);
        step = null;
      }
    }
    return step;
  }
  /**
   * Adds a {@link Step} entity by the provided context. A {@link Step} will not be created if
   * {@code ExecutionContext.isMonitored()} returns false.
   *
   * @param context The context of the execution which defines visibility and execution method.
   * @param stepName The name of the step.
   * @param description A presentation name for the step. If not provided, the presentation name is
   *     resolved by the {@code stepName}.
   * @param isExternal Indicates if the step is invoked by a plug-in
   * @return
   */
  public static Step addStep(
      ExecutionContext context, StepEnum stepName, String description, boolean isExternal) {
    if (context == null) {
      return null;
    }
    Step step = null;

    if (context.isMonitored()) {
      if (description == null) {
        description = ExecutionMessageDirector.getInstance().getStepMessage(stepName);
      }

      try {
        Job job = context.getJob();
        if (context.getExecutionMethod() == ExecutionMethod.AsJob && job != null) {
          step = job.addStep(stepName, description);
          try {
            step.setExternal(isExternal);
            JobRepositoryFactory.getJobRepository().saveStep(step);
          } catch (Exception e) {
            log.errorFormat(
                "Failed to save new step {0} for job {1}, {2}.",
                stepName.name(), job.getId(), job.getActionType().name(), e);
            job.getSteps().remove(step);
            step = null;
          }
        } else {
          Step contextStep = context.getStep();
          if (context.getExecutionMethod() == ExecutionMethod.AsStep && contextStep != null) {
            step = addSubStep(contextStep, stepName, description);
            step.setExternal(isExternal);
          }
        }
      } catch (Exception e) {
        log.error(e);
      }
    }
    return step;
  }
  /**
   * Finalizes Job with VDSM tasks, as this case requires verification that no other steps are
   * running in order to close the entire Job
   *
   * @param executionContext The context of the execution which defines how the job should be ended
   * @param exitStatus Indicates if the execution described by the job ended successfully or not.
   */
  public static void endTaskJob(ExecutionContext context, boolean exitStatus) {
    if (context == null) {
      return;
    }

    try {
      if (context.getExecutionMethod() == ExecutionMethod.AsJob && context.getJob() != null) {
        endJob(context, exitStatus);
      } else {
        Step parentStep = context.getStep();
        if (context.getExecutionMethod() == ExecutionMethod.AsStep && parentStep != null) {
          Step finalizingStep = parentStep.getStep(StepEnum.FINALIZING);
          if (finalizingStep != null) {
            finalizingStep.markStepEnded(exitStatus);
            JobRepositoryFactory.getJobRepository().updateStep(finalizingStep);
          }
          parentStep.markStepEnded(exitStatus);
          JobRepositoryFactory.getJobRepository().updateStep(parentStep);

          List<Step> steps =
              DbFacade.getInstance().getStepDao().getStepsByJobId(parentStep.getJobId());
          boolean hasChildStepsRunning = false;
          for (Step step : steps) {
            if (step.getStatus() == JobExecutionStatus.STARTED && step.getParentStepId() != null) {
              hasChildStepsRunning = true;
              break;
            }
          }
          if (!hasChildStepsRunning) {
            endJob(
                exitStatus, JobRepositoryFactory.getJobRepository().getJob(parentStep.getJobId()));
          }
        }
      }
    } catch (RuntimeException e) {
      log.error(e);
    }
  }
 @Override
 protected boolean canDoAction() {
   boolean retValue = true;
   step = getStepDao().get(getParameters().getId());
   if (step == null) {
     retValue = false;
     addCanDoActionMessage(VdcBllMessages.ACTION_TYPE_NO_STEP);
   } else if (!step.isExternal()) {
     retValue = false;
     addCanDoActionMessage(VdcBllMessages.ACTION_TYPE_NOT_EXTERNAL);
   } else {
     job = getJobDao().get(step.getJobId());
     if (job == null) {
       retValue = false;
       addCanDoActionMessage(VdcBllMessages.ACTION_TYPE_NO_JOB);
     }
     if (!retValue) {
       addCanDoActionMessage(VdcBllMessages.VAR__ACTION__END);
       addCanDoActionMessage(VdcBllMessages.VAR__TYPE__EXTERNAL_STEP);
     }
   }
   return retValue;
 }
  /**
   * Method should be called when finalizing the command. The execution step is being ended with
   * success and the finalization step is started.
   *
   * @param executionContext The context of the job
   * @return A created instance of the Finalizing step
   */
  public static Step startFinalizingStep(ExecutionContext executionContext) {
    if (executionContext == null) {
      return null;
    }
    Step step = null;

    try {
      if (executionContext.getExecutionMethod() == ExecutionMethod.AsJob) {
        Job job = executionContext.getJob();
        if (job != null) {
          Step executingStep = job.getStep(StepEnum.EXECUTING);
          Step finalizingStep =
              job.addStep(
                  StepEnum.FINALIZING,
                  ExecutionMessageDirector.getInstance().getStepMessage(StepEnum.FINALIZING));

          if (executingStep != null) {
            executingStep.markStepEnded(true);
            JobRepositoryFactory.getJobRepository()
                .updateExistingStepAndSaveNewStep(executingStep, finalizingStep);
          } else {
            JobRepositoryFactory.getJobRepository().saveStep(finalizingStep);
          }
        }
      } else if (executionContext.getExecutionMethod() == ExecutionMethod.AsStep) {
        Step parentStep = executionContext.getStep();
        if (parentStep != null) {
          Step executingStep = parentStep.getStep(StepEnum.EXECUTING);
          Step finalizingStep =
              parentStep.addStep(
                  StepEnum.FINALIZING,
                  ExecutionMessageDirector.getInstance().getStepMessage(StepEnum.FINALIZING));
          if (executingStep != null) {
            executingStep.markStepEnded(true);
            JobRepositoryFactory.getJobRepository()
                .updateExistingStepAndSaveNewStep(executingStep, finalizingStep);
          } else {
            JobRepositoryFactory.getJobRepository().saveStep(finalizingStep);
          }
        }
      }
    } catch (Exception e) {
      log.error(e);
    }
    return step;
  }
  /**
   * Gets a list of {@link Step} entities ordered by:
   * <li>parent step id, preceded by nulls
   * <li>step number
   *
   * @param steps
   * @return a collection of the steps.
   */
  private List<Step> buildStepsTree(List<Step> steps) {
    List<Step> jobDirectSteps = new ArrayList<Step>();

    // a map of parent step id and a list of child-steps
    Map<NGuid, List<Step>> parentStepMap = new HashMap<NGuid, List<Step>>();

    for (Step step : steps) {
      if (step.getParentStepId() == null) {
        jobDirectSteps.add(step);
      } else {
        MultiValueMapUtils.addToMap(step.getParentStepId(), step, parentStepMap);
      }
    }

    for (Step step : steps) {
      step.setSteps(parentStepMap.get(step.getId()));
    }
    return jobDirectSteps;
  }
  /**
   * Adds a {@link Step} entity by the provided context as a child step of a given parent step. A
   * {@link Step} will not be created if {@code ExecutionContext.isMonitored()} returns false.
   *
   * @param context The context of the execution which defines visibility and execution method.
   * @param parentStep The parent step which the new step will be added as its child.
   * @param newStepName The name of the step.
   * @param description A presentation name for the step. If not provided, the presentation name is
   *     resolved by the {@code stepName}.
   * @param isExternal Indicates if the step is invoked by a plug-in
   * @return
   */
  public static Step addSubStep(
      ExecutionContext context,
      Step parentStep,
      StepEnum newStepName,
      String description,
      boolean isExternal) {
    Step step = null;

    if (context == null || parentStep == null) {
      return null;
    }

    try {
      if (context.isMonitored()) {
        if (description == null) {
          description = ExecutionMessageDirector.getInstance().getStepMessage(newStepName);
        }

        if (context.getExecutionMethod() == ExecutionMethod.AsJob) {
          if (DbFacade.getInstance().getStepDao().exists(parentStep.getId())) {
            if (parentStep.getJobId().equals(context.getJob().getId())) {
              step = parentStep.addStep(newStepName, description);
            }
          }
        } else if (context.getExecutionMethod() == ExecutionMethod.AsStep) {
          step = parentStep.addStep(newStepName, description);
        }
      }
      if (step != null) {
        step.setExternal(isExternal);
        JobRepositoryFactory.getJobRepository().saveStep(step);
      }
    } catch (Exception e) {
      log.error(e);
    }
    return step;
  }