@Override
  public WorkspaceItem abort(Context c, XmlWorkflowItem wi, EPerson e)
      throws AuthorizeException, SQLException, IOException {
    if (!authorizeService.isAdmin(c)) {
      throw new AuthorizeException("You must be an admin to abort a workflow");
    }

    c.turnOffAuthorisationSystem();
    // Restore permissions for the submitter
    // convert into personal workspace
    WorkspaceItem wsi = returnToWorkspace(c, wi);

    log.info(
        LogManager.getHeader(
            c,
            "abort_workflow",
            "workflow_item_id="
                + wi.getID()
                + "item_id="
                + wsi.getItem().getID()
                + "collection_id="
                + wi.getCollection().getID()
                + "eperson_id="
                + e.getID()));

    c.restoreAuthSystemState();
    return wsi;
  }
  /**
   * Commit the contained item to the main archive. The item is associated with the relevant
   * collection, added to the search index, and any other tasks such as assigning dates are
   * performed.
   *
   * @param context The relevant DSpace Context.
   * @param wfi workflow item
   * @return the fully archived item.
   * @throws IOException A general class of exceptions produced by failed or interrupted I/O
   *     operations.
   * @throws SQLException An exception that provides information on a database access error or other
   *     errors.
   * @throws AuthorizeException Exception indicating the current user of the context does not have
   *     permission to perform a particular action.
   */
  @Override
  public Item archive(Context context, XmlWorkflowItem wfi)
      throws SQLException, IOException, AuthorizeException {
    // FIXME: Check auth
    Item item = wfi.getItem();
    Collection collection = wfi.getCollection();

    // Remove (if any) the workflowItemroles for this item
    workflowItemRoleService.deleteForWorkflowItem(context, wfi);

    log.info(
        LogManager.getHeader(
            context,
            "archive_item",
            "workflow_item_id="
                + wfi.getID()
                + "item_id="
                + item.getID()
                + "collection_id="
                + collection.getID()));

    installItemService.installItem(context, wfi);

    // Notify
    notifyOfArchive(context, item, collection);

    // Clear any remaining workflow metadata
    itemService.clearMetadata(
        context, item, WorkflowRequirementsService.WORKFLOW_SCHEMA, Item.ANY, Item.ANY, Item.ANY);
    itemService.update(context, item);

    // Log the event
    log.info(
        LogManager.getHeader(
            context,
            "install_item",
            "workflow_item_id=" + wfi.getID() + ", item_id=" + item.getID() + "handle=FIXME"));

    return item;
  }
  protected void activateFirstStep(
      Context context, Workflow wf, Step firstStep, XmlWorkflowItem wfi)
      throws AuthorizeException, IOException, SQLException, WorkflowException,
          WorkflowConfigurationException {
    WorkflowActionConfig firstActionConfig = firstStep.getUserSelectionMethod();
    firstActionConfig.getProcessingAction().activate(context, wfi);
    log.info(
        LogManager.getHeader(
            context,
            "start_workflow",
            firstActionConfig.getProcessingAction()
                + " workflow_item_id="
                + wfi.getID()
                + "item_id="
                + wfi.getItem().getID()
                + "collection_id="
                + wfi.getCollection().getID()));

    // record the start of the workflow w/provenance message
    recordStart(context, wfi.getItem(), firstActionConfig.getProcessingAction());

    // Fire an event !
    logWorkflowEvent(
        context,
        firstStep.getWorkflow().getID(),
        null,
        null,
        wfi,
        null,
        firstStep,
        firstActionConfig);

    // If we don't have a UI activate it
    if (!firstActionConfig.requiresUI()) {
      ActionResult outcome =
          firstActionConfig.getProcessingAction().execute(context, wfi, firstStep, null);
      processOutcome(context, null, wf, firstStep, firstActionConfig, outcome, wfi, true);
    }
  }
  /**
   * Return the workflow item to the workspace of the submitter. The workflow item is removed, and a
   * workspace item created.
   *
   * @param c Context
   * @param wfi WorkflowItem to be 'dismantled'
   * @return the workspace item
   * @throws java.io.IOException ...
   * @throws java.sql.SQLException ...
   * @throws org.dspace.authorize.AuthorizeException ...
   */
  protected WorkspaceItem returnToWorkspace(Context c, XmlWorkflowItem wfi)
      throws SQLException, IOException, AuthorizeException {
    // authorize a DSpaceActions.REJECT
    // stop workflow
    deleteAllTasks(c, wfi);

    c.turnOffAuthorisationSystem();
    // Also clear all info for this step
    workflowRequirementsService.clearInProgressUsers(c, wfi);

    // Remove (if any) the workflowItemroles for this item
    workflowItemRoleService.deleteForWorkflowItem(c, wfi);

    Item myitem = wfi.getItem();
    // Restore permissions for the submitter
    grantUserAllItemPolicies(c, myitem, myitem.getSubmitter());

    // FIXME: How should this interact with the workflow system?
    // FIXME: Remove license
    // FIXME: Provenance statement?
    // Create the new workspace item row
    WorkspaceItem workspaceItem = workspaceItemService.create(c, wfi);
    workspaceItem.setMultipleFiles(wfi.hasMultipleFiles());
    workspaceItem.setMultipleTitles(wfi.hasMultipleTitles());
    workspaceItem.setPublishedBefore(wfi.isPublishedBefore());
    workspaceItemService.update(c, workspaceItem);

    // myitem.update();
    log.info(
        LogManager.getHeader(
            c,
            "return_to_workspace",
            "workflow_item_id=" + wfi.getID() + "workspace_item_id=" + workspaceItem.getID()));

    // Now remove the workflow object manually from the database
    xmlWorkflowItemService.deleteWrapper(c, wfi);
    return workspaceItem;
  }
  /**
   * notify the submitter that the item is archived
   *
   * @param context The relevant DSpace Context.
   * @param item which item was archived
   * @param coll collection name to display in template
   * @throws SQLException An exception that provides information on a database access error or other
   *     errors.
   * @throws IOException A general class of exceptions produced by failed or interrupted I/O
   *     operations.
   */
  protected void notifyOfArchive(Context context, Item item, Collection coll)
      throws SQLException, IOException {
    try {
      // Get submitter
      EPerson ep = item.getSubmitter();
      // Get the Locale
      Locale supportedLocale = I18nUtil.getEPersonLocale(ep);
      Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "submit_archive"));

      // Get the item handle to email to user
      String handle = handleService.findHandle(context, item);

      // Get title
      List<MetadataValue> titles =
          itemService.getMetadata(item, MetadataSchema.DC_SCHEMA, "title", null, Item.ANY);
      String title = "";
      try {
        title = I18nUtil.getMessage("org.dspace.workflow.WorkflowManager.untitled");
      } catch (MissingResourceException e) {
        title = "Untitled";
      }
      if (titles.size() > 0) {
        title = titles.iterator().next().getValue();
      }

      email.addRecipient(ep.getEmail());
      email.addArgument(title);
      email.addArgument(coll.getName());
      email.addArgument(handleService.getCanonicalForm(handle));

      email.send();
    } catch (MessagingException e) {
      log.warn(
          LogManager.getHeader(
              context, "notifyOfArchive", "cannot email user" + " item_id=" + item.getID()));
    }
  }
 /*
  * Executes an action and returns the next.
  */
 @Override
 public WorkflowActionConfig doState(
     Context c,
     EPerson user,
     HttpServletRequest request,
     int workflowItemId,
     Workflow workflow,
     WorkflowActionConfig currentActionConfig)
     throws SQLException, AuthorizeException, IOException, MessagingException, WorkflowException {
   try {
     XmlWorkflowItem wi = xmlWorkflowItemService.find(c, workflowItemId);
     Step currentStep = currentActionConfig.getStep();
     if (currentActionConfig.getProcessingAction().isAuthorized(c, request, wi)) {
       ActionResult outcome =
           currentActionConfig.getProcessingAction().execute(c, wi, currentStep, request);
       return processOutcome(
           c, user, workflow, currentStep, currentActionConfig, outcome, wi, false);
     } else {
       throw new AuthorizeException("You are not allowed to to perform this task.");
     }
   } catch (WorkflowConfigurationException e) {
     log.error(
         LogManager.getHeader(
             c,
             "error while executing state",
             "workflow:  "
                 + workflow.getID()
                 + " action: "
                 + currentActionConfig.getId()
                 + " workflowItemId: "
                 + workflowItemId),
         e);
     WorkflowUtils.sendAlert(request, e);
     throw new WorkflowException(e);
   }
 }
  protected void notifyOfReject(Context c, XmlWorkflowItem wi, EPerson e, String reason) {
    try {
      // Get the item title
      String title = wi.getItem().getName();

      // Get the collection
      Collection coll = wi.getCollection();

      // Get rejector's name
      String rejector = getEPersonName(e);
      Locale supportedLocale = I18nUtil.getEPersonLocale(e);
      Email email = Email.getEmail(I18nUtil.getEmailFilename(supportedLocale, "submit_reject"));

      email.addRecipient(wi.getSubmitter().getEmail());
      email.addArgument(title);
      email.addArgument(coll.getName());
      email.addArgument(rejector);
      email.addArgument(reason);
      email.addArgument(ConfigurationManager.getProperty("dspace.url") + "/mydspace");

      email.send();
    } catch (Exception ex) {
      // log this email error
      log.warn(
          LogManager.getHeader(
              c,
              "notify_of_reject",
              "cannot email user"
                  + " eperson_id"
                  + e.getID()
                  + " eperson_email"
                  + e.getEmail()
                  + " workflow_item_id"
                  + wi.getID()));
    }
  }
  @Override
  public WorkspaceItem sendWorkflowItemBackSubmission(
      Context context, XmlWorkflowItem wi, EPerson e, String provenance, String rejection_message)
      throws SQLException, AuthorizeException, IOException {

    String workflowID = null;
    String currentStepId = null;
    String currentActionConfigId = null;
    ClaimedTask claimedTask = claimedTaskService.findByWorkflowIdAndEPerson(context, wi, e);
    if (claimedTask != null) {
      // Log it
      workflowID = claimedTask.getWorkflowID();
      currentStepId = claimedTask.getStepID();
      currentActionConfigId = claimedTask.getActionID();
    }
    context.turnOffAuthorisationSystem();

    // rejection provenance
    Item myitem = wi.getItem();

    // Get current date
    String now = DCDate.getCurrent().toString();

    // Get user's name + email address
    String usersName = getEPersonName(e);

    // Here's what happened
    String provDescription =
        provenance
            + " Rejected by "
            + usersName
            + ", reason: "
            + rejection_message
            + " on "
            + now
            + " (GMT) ";

    // Add to item as a DC field
    itemService.addMetadata(
        context,
        myitem,
        MetadataSchema.DC_SCHEMA,
        "description",
        "provenance",
        "en",
        provDescription);

    // Clear any workflow schema related metadata
    itemService.clearMetadata(
        context, myitem, WorkflowRequirementsService.WORKFLOW_SCHEMA, Item.ANY, Item.ANY, Item.ANY);

    itemService.update(context, myitem);

    // convert into personal workspace
    WorkspaceItem wsi = returnToWorkspace(context, wi);

    // notify that it's been rejected
    notifyOfReject(context, wi, e, rejection_message);
    log.info(
        LogManager.getHeader(
            context,
            "reject_workflow",
            "workflow_item_id="
                + wi.getID()
                + "item_id="
                + wi.getItem().getID()
                + "collection_id="
                + wi.getCollection().getID()
                + "eperson_id="
                + e.getID()));

    logWorkflowEvent(context, workflowID, currentStepId, currentActionConfigId, wi, e, null, null);

    context.restoreAuthSystemState();
    return wsi;
  }
  protected void logWorkflowEvent(
      Context c,
      String workflowId,
      String previousStepId,
      String previousActionConfigId,
      XmlWorkflowItem wfi,
      EPerson actor,
      Step newStep,
      WorkflowActionConfig newActionConfig)
      throws SQLException {
    try {
      // Fire an event so we can log our action !
      Item item = wfi.getItem();
      Collection myCollection = wfi.getCollection();
      String workflowStepString = null;

      List<EPerson> currentEpersonOwners = new ArrayList<EPerson>();
      List<Group> currentGroupOwners = new ArrayList<Group>();
      // These are only null if our item is sent back to the submission
      if (newStep != null && newActionConfig != null) {
        workflowStepString = workflowId + "." + newStep.getId() + "." + newActionConfig.getId();

        // Retrieve the current owners of the task
        List<ClaimedTask> claimedTasks = claimedTaskService.find(c, wfi, newStep.getId());
        List<PoolTask> pooledTasks = poolTaskService.find(c, wfi);
        for (PoolTask poolTask : pooledTasks) {
          if (poolTask.getEperson() != null) {
            currentEpersonOwners.add(poolTask.getEperson());
          } else {
            currentGroupOwners.add(poolTask.getGroup());
          }
        }
        for (ClaimedTask claimedTask : claimedTasks) {
          currentEpersonOwners.add(claimedTask.getOwner());
        }
      }
      String previousWorkflowStepString = null;
      if (previousStepId != null && previousActionConfigId != null) {
        previousWorkflowStepString =
            workflowId + "." + previousStepId + "." + previousActionConfigId;
      }

      // Fire our usage event !
      UsageWorkflowEvent usageWorkflowEvent =
          new UsageWorkflowEvent(
              c, item, wfi, workflowStepString, previousWorkflowStepString, myCollection, actor);

      usageWorkflowEvent.setEpersonOwners(
          currentEpersonOwners.toArray(new EPerson[currentEpersonOwners.size()]));
      usageWorkflowEvent.setGroupOwners(
          currentGroupOwners.toArray(new Group[currentGroupOwners.size()]));

      DSpaceServicesFactory.getInstance().getEventService().fireEvent(usageWorkflowEvent);
    } catch (Exception e) {
      // Catch all errors we do not want our workflow to crash because the logging threw an
      // exception
      log.error(
          LogManager.getHeader(
              c, "Error while logging workflow event", "Workflow Item: " + wfi.getID()),
          e);
    }
  }
  @Override
  public WorkflowActionConfig processOutcome(
      Context c,
      EPerson user,
      Workflow workflow,
      Step currentStep,
      WorkflowActionConfig currentActionConfig,
      ActionResult currentOutcome,
      XmlWorkflowItem wfi,
      boolean enteredNewStep)
      throws IOException, AuthorizeException, SQLException, WorkflowException {
    if (currentOutcome.getType() == ActionResult.TYPE.TYPE_PAGE
        || currentOutcome.getType() == ActionResult.TYPE.TYPE_ERROR) {
      // Our outcome is a page or an error, so return our current action
      c.restoreAuthSystemState();
      return currentActionConfig;
    } else if (currentOutcome.getType() == ActionResult.TYPE.TYPE_CANCEL
        || currentOutcome.getType() == ActionResult.TYPE.TYPE_SUBMISSION_PAGE) {
      // We either pressed the cancel button or got an order to return to the submission page, so
      // don't return an action
      // By not returning an action we ensure ourselfs that we go back to the submission page
      c.restoreAuthSystemState();
      return null;
    } else if (currentOutcome.getType() == ActionResult.TYPE.TYPE_OUTCOME) {
      Step nextStep = null;
      WorkflowActionConfig nextActionConfig = null;
      try {
        // We have completed our action search & retrieve the next action
        if (currentOutcome.getResult() == ActionResult.OUTCOME_COMPLETE) {
          nextActionConfig = currentStep.getNextAction(currentActionConfig);
        }

        if (nextActionConfig != null) {
          // We remain in the current step since an action is found
          nextStep = currentStep;
          nextActionConfig.getProcessingAction().activate(c, wfi);
          if (nextActionConfig.requiresUI() && !enteredNewStep) {
            createOwnedTask(c, wfi, currentStep, nextActionConfig, user);
            return nextActionConfig;
          } else if (nextActionConfig.requiresUI() && enteredNewStep) {
            // We have entered a new step and have encountered a UI, return null since the current
            // user doesn't have anything to do with this
            c.restoreAuthSystemState();
            return null;
          } else {
            ActionResult newOutcome =
                nextActionConfig.getProcessingAction().execute(c, wfi, currentStep, null);
            return processOutcome(
                c, user, workflow, currentStep, nextActionConfig, newOutcome, wfi, enteredNewStep);
          }
        } else if (enteredNewStep) {
          // If the user finished his/her step, we keep processing until there is a UI step action
          // or no step at all
          nextStep = workflow.getNextStep(c, wfi, currentStep, currentOutcome.getResult());
          c.turnOffAuthorisationSystem();
          nextActionConfig = processNextStep(c, user, workflow, currentOutcome, wfi, nextStep);
          // If we require a user interface return null so that the user is redirected to the
          // "submissions page"
          if (nextActionConfig == null || nextActionConfig.requiresUI()) {
            return null;
          } else {
            return nextActionConfig;
          }
        } else {
          ClaimedTask task = claimedTaskService.findByWorkflowIdAndEPerson(c, wfi, user);

          // Check if we have a task for this action (might not be the case with automatic steps)
          // First add it to our list of finished users, since no more actions remain
          workflowRequirementsService.addFinishedUser(c, wfi, user);
          c.turnOffAuthorisationSystem();
          // Check if our requirements have been met
          if ((currentStep.isFinished(c, wfi)
                  && currentOutcome.getResult() == ActionResult.OUTCOME_COMPLETE)
              || currentOutcome.getResult() != ActionResult.OUTCOME_COMPLETE) {
            // Delete all the table rows containing the users who performed this task
            workflowRequirementsService.clearInProgressUsers(c, wfi);
            // Remove all the tasks
            deleteAllTasks(c, wfi);

            nextStep = workflow.getNextStep(c, wfi, currentStep, currentOutcome.getResult());

            nextActionConfig = processNextStep(c, user, workflow, currentOutcome, wfi, nextStep);
            // If we require a user interface return null so that the user is redirected to the
            // "submissions page"
            if (nextActionConfig == null || nextActionConfig.requiresUI()) {
              return null;
            } else {
              return nextActionConfig;
            }
          } else {
            // We are done with our actions so go to the submissions page but remove action
            // ClaimedAction first
            deleteClaimedTask(c, wfi, task);
            c.restoreAuthSystemState();
            nextStep = currentStep;
            nextActionConfig = currentActionConfig;
            return null;
          }
        }
      } catch (Exception e) {
        log.error("error while processing workflow outcome", e);
        e.printStackTrace();
      } finally {
        if ((nextStep != null && currentStep != null && nextActionConfig != null)
            || (wfi.getItem().isArchived() && currentStep != null)) {
          logWorkflowEvent(
              c,
              currentStep.getWorkflow().getID(),
              currentStep.getId(),
              currentActionConfig.getId(),
              wfi,
              user,
              nextStep,
              nextActionConfig);
        }
      }
    }

    log.error(LogManager.getHeader(c, "Invalid step outcome", "Workflow item id: " + wfi.getID()));
    throw new WorkflowException("Invalid step outcome");
  }