// In case of notifications coming from post notifications, start and end time need to be
 // populated.
 private void updateContextWithTime(WorkflowExecutionContext context) {
   try {
     InstancesResult result =
         WorkflowEngineFactory.getWorkflowEngine()
             .getJobDetails(context.getClusterName(), context.getWorkflowId());
     Date startTime = result.getInstances()[0].startTime;
     Date endTime = result.getInstances()[0].endTime;
     Date now = new Date();
     if (startTime == null) {
       startTime = now;
     }
     if (endTime == null) {
       endTime = now;
     }
     context.setValue(WorkflowExecutionArgs.WF_START_TIME, Long.toString(startTime.getTime()));
     context.setValue(WorkflowExecutionArgs.WF_END_TIME, Long.toString(endTime.getTime()));
   } catch (FalconException e) {
     LOG.error(
         "Unable to retrieve job details for "
             + context.getWorkflowId()
             + " on cluster "
             + context.getClusterName(),
         e);
   }
 }
  // The method retrieves the conf from the cache if it is in cache.
  // Else, queries WF Engine to retrieve the conf of the workflow
  private void updateContextFromWFConf(WorkflowExecutionContext context) {
    try {
      Properties wfProps = contextMap.get(context.getWorkflowId());
      if (wfProps == null) {
        Entity entity =
            CONFIG_STORE.get(EntityType.valueOf(context.getEntityType()), context.getEntityName());
        // Entity can be null in case of delete. Engine will generate notifications for instance
        // kills.
        // But, the entity would no longer be in the config store.
        if (entity == null) {
          return;
        }
        for (String cluster : EntityUtil.getClustersDefinedInColos(entity)) {
          try {
            InstancesResult.Instance[] instances =
                WorkflowEngineFactory.getWorkflowEngine()
                    .getJobDetails(cluster, context.getWorkflowId())
                    .getInstances();
            if (instances != null && instances.length > 0) {
              wfProps = getWFProps(instances[0].getWfParams());
              // Required by RetryService. But, is not part of conf.
              wfProps.setProperty(
                  WorkflowExecutionArgs.RUN_ID.getName(),
                  Integer.toString(instances[0].getRunId()));
            }
          } catch (FalconException e) {
            // Do Nothing. The workflow may not have been deployed on this cluster.
            continue;
          }
          contextMap.put(context.getWorkflowId(), wfProps);
        }
      }

      // No extra props to enhance the context with.
      if (wfProps == null || wfProps.isEmpty()) {
        return;
      }

      for (WorkflowExecutionArgs arg : WorkflowExecutionArgs.values()) {
        if (wfProps.containsKey(arg.getName())) {
          context.setValue(arg, wfProps.getProperty(arg.getName()));
        }
      }

    } catch (FalconException e) {
      LOG.error("Unable to retrieve entity {} of type {} from config store.", e);
    }
  }
  // This method handles both success and failure notifications.
  private void notifyWorkflowEnd(WorkflowExecutionContext context) {
    // Need to distinguish notification from post processing for backward compatibility
    if (context.getContextType() == WorkflowExecutionContext.Type.POST_PROCESSING) {
      boolean engineNotifEnabled = false;
      try {
        engineNotifEnabled =
            WorkflowEngineFactory.getWorkflowEngine()
                .isNotificationEnabled(context.getClusterName(), context.getWorkflowId());
      } catch (FalconException e) {
        LOG.debug(
            "Received error while checking if notification is enabled. "
                + "Hence, assuming notification is not enabled.");
      }
      // Ignore the message from post processing as there will be one more from Oozie.
      if (engineNotifEnabled) {
        LOG.info("Ignoring message from post processing as engine notification is enabled.");
        return;
      } else {
        updateContextWithTime(context);
      }
    } else {
      updateContextFromWFConf(context);
    }

    LOG.debug("Sending workflow end notification to listeners with context : {} ", context);

    for (WorkflowExecutionListener listener : listeners) {
      try {
        if (context.hasWorkflowSucceeded()) {
          listener.onSuccess(context);
          instrumentAlert(context);
        } else {
          listener.onFailure(context);
          if (context.hasWorkflowBeenKilled() || context.hasWorkflowFailed()) {
            instrumentAlert(context);
          }
        }
      } catch (Throwable t) {
        // do not rethrow as other listeners do not get a chance
        LOG.error("Error in listener {}", listener.getClass().getName(), t);
      }
    }

    contextMap.remove(context.getWorkflowId());
  }