/**
   * 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);
    }
  }
  /**
   * Prepares the monitoring objects for the command by the default behavior:
   *
   * <ul>
   *   <li>{@link ExecutionContext} determines how the command should be monitored. By default,
   *       non-internal commands will be associated with {@code Job} to represent the command
   *       execution. Internal commands will not be monitored by default, therefore the {@code
   *       ExecutionContext} is created as non-monitored context.
   *   <li>{@link Job} is created for monitored actions
   * </ul>
   *
   * @param command The created instance of the command (can't be <code>null</code>).
   * @param actionType The action type of the command
   * @param runAsInternal Indicates if the command should be run as internal action or not
   * @param hasCorrelationId Indicates if the current command was executed under a correlation-ID
   */
  public static void prepareCommandForMonitoring(
      CommandBase<?> command, VdcActionType actionType, boolean runAsInternal) {

    ExecutionContext context = command.getExecutionContext();
    if (context == null) {
      context = new ExecutionContext();
    }

    try {
      boolean isMonitored = shouldMonitorCommand(actionType, runAsInternal);

      // A monitored job is created for monitored external flows
      if (isMonitored || context.isJobRequired()) {
        Job job = getJob(command, actionType);
        context.setExecutionMethod(ExecutionMethod.AsJob);
        context.setJob(job);
        command.setExecutionContext(context);
        command.setJobId(job.getId());
        context.setMonitored(true);
      }
    } catch (Exception e) {
      log.errorFormat(
          "Failed to prepare command of type {0} for monitoring due to error {1}",
          actionType.name(), ExceptionUtils.getMessage(e), 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 void endJob(boolean exitStatus, Job job) {
   job.markJobEnded(exitStatus);
   try {
     JobRepositoryFactory.getJobRepository().updateCompletedJobAndSteps(job);
   } catch (Exception e) {
     log.errorFormat("Failed to end Job {0}, {1}", job.getId(), job.getActionType().name(), e);
   }
 }
 /**
  * Mark the Job as an Async Job which should be terminated by external process to the current
  * command scope.
  *
  * @param executionContext The context which describe the running job.
  * @param isAsync indicates if the job should be ended by current action
  */
 public static void setAsyncJob(ExecutionContext executionContext, boolean isAsync) {
   if (executionContext == null) {
     return;
   }
   Job job = executionContext.getJob();
   if (executionContext.getExecutionMethod() == ExecutionMethod.AsJob && job != null) {
     job.setIsAsyncJob(isAsync);
   }
 }
 @Override
 public Job getJob(final Guid jobId) {
   Job job = jobDao.get(jobId);
   if (job != null) {
     Map<Guid, VdcObjectType> jobSubjectEntity =
         jobSubjectEntityDao.getJobSubjectEntityByJobId(jobId);
     job.setJobSubjectEntities(jobSubjectEntity);
   }
   return job;
 }
  @Override
  public List<Job> getJobsByEntityAndAction(Guid entityId, VdcActionType actionType) {
    List<Job> jobList = new ArrayList<Job>();
    List<Guid> jobIdsList = jobSubjectEntityDao.getJobIdByEntityId(entityId);

    for (Guid jobId : jobIdsList) {
      Job job = jobDao.get(jobId);
      if (job != null && job.getActionType() == actionType) {
        jobList.add(job);
      }
    }
    return jobList;
  }
 /**
  * Updates Job for the same entity for a specific action as completed with a given exit status.
  *
  * @param entityId The entity to search for its jobs
  * @param actionType The action type to search for
  * @param status The exist status to be set for the job
  */
 public static void updateSpecificActionJobCompleted(
     Guid entityId, VdcActionType actionType, boolean status) {
   try {
     List<Job> jobs =
         JobRepositoryFactory.getJobRepository().getJobsByEntityAndAction(entityId, actionType);
     for (Job job : jobs) {
       if (job.getStatus() == JobExecutionStatus.STARTED) job.markJobEnded(status);
       JobRepositoryFactory.getJobRepository().updateCompletedJobAndSteps(job);
     }
   } catch (RuntimeException e) {
     log.error(e);
   }
 }
  @Override
  public Job getJobWithSteps(final Guid jobId) {
    Job job = jobDao.get(jobId);
    if (job != null) {
      Map<Guid, VdcObjectType> jobSubjectEntity =
          jobSubjectEntityDao.getJobSubjectEntityByJobId(jobId);
      job.setJobSubjectEntities(jobSubjectEntity);

      List<Step> steps = stepDao.getStepsByJobId(jobId);
      if (!steps.isEmpty()) {
        job.setSteps(buildStepsTree(steps));
      }
    }
    return job;
  }
  /**
   * 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;
  }
  /**
   * Creates and returns an instance of {@link Job} entity.
   *
   * @param actionType The action type the job entity represents.
   * @param command The {@code CommandBase} instance which the job entity describes.
   * @return An initialized {@code Job} instance.
   */
  public static Job createJob(VdcActionType actionType, CommandBase<?> command) {
    Job job = new Job();

    job.setId(Guid.newGuid());
    job.setActionType(actionType);
    job.setDescription(
        ExecutionMessageDirector.resolveJobMessage(actionType, command.getJobMessageProperties()));
    job.setJobSubjectEntities(getSubjectEntities(command.getPermissionCheckSubjects()));
    job.setOwnerId(command.getUserId());
    job.setStatus(JobExecutionStatus.STARTED);
    job.setStartTime(new Date());
    job.setCorrelationId(command.getCorrelationId());

    return job;
  }
  /**
   * 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;
  }