@Override
  public List<String> buildListForFYI(AwardDocument awardDocument) throws WorkflowException {

    WorkflowDocument document = awardDocument.getDocumentHeader().getWorkflowDocument();
    RoutingReportCriteria reportCriteria =
        RoutingReportCriteria.Builder.createByDocumentId(document.getDocumentId()).build();
    // gather the IDs for action requests that predate the simulation
    DocumentRouteHeaderValue routeHeader =
        KEWServiceLocator.getRouteHeaderService().getRouteHeader(document.getDocumentId());
    Set<String> preexistingActionRequestIds = getActionRequestIds(routeHeader);

    // run the simulation via WorkflowUtility
    DocumentDetail documentDetail = workflowUtility.executeSimulation(reportCriteria);

    // fabricate our ActionRequestValueS from the results
    List<ActionRequestValue> actionRequests =
        reconstituteActionRequestValues(documentDetail, preexistingActionRequestIds);

    List<String> actionIds = new ArrayList<String>();
    for (ActionRequestValue request : actionRequests) {
      if (request.isGroupRequest()) {
        actionIds.addAll(
            KimApiServiceLocator.getGroupService().getMemberPrincipalIds(request.getGroupId()));
      }
      if (request.isUserRequest()) {
        actionIds.add(request.getPrincipalId());
      }
    }
    return actionIds;
  }
  /**
   * Tests that an exception is not thrown if you try to execute a "route" command on a document you
   * did not initiate.
   */
  @Test
  public void testRouteDocumentAsNonInitiatorUser() throws Exception {
    WorkflowDocument firstDocument =
        WorkflowDocumentFactory.createDocument(
            getPrincipalIdForName("user1"), DOCUMENT_TYPE_POLICY_TEST_NAME);
    WorkflowDocument document =
        WorkflowDocumentFactory.loadDocument(
            getPrincipalIdForName("user2"), firstDocument.getDocumentId());
    try {
      document.route("");
    } catch (Exception e) {
      e.printStackTrace();
      fail(
          "Exception thrown but should not have have been... Exception was of type "
              + e.getClass().getName()
              + " and message was "
              + e.getMessage());
    }
    document =
        WorkflowDocumentFactory.loadDocument(
            getPrincipalIdForName("user1"), firstDocument.getDocumentId());
    assertEquals(
        "Document should be in Enroute status.", DocumentStatus.ENROUTE, document.getStatus());

    // verify that there is 1 action taken
    Collection<ActionTakenValue> actionTakens =
        KEWServiceLocator.getActionTakenService().findByDocumentId(document.getDocumentId());
    assertEquals("There should be 1 action taken.", 1, actionTakens.size());
  }
  /**
   * Tests that an exception is thrown if you try to execute a "route" command on an already routed
   * document.
   */
  @Test
  public void testRouteAlreadyRoutedDocument() throws Exception {
    WorkflowDocument document =
        WorkflowDocumentFactory.createDocument(getPrincipalIdForName("user1"), DOCUMENT_TYPE_NAME);
    document.route("");

    assertTrue("Document should be ENROUTE.", document.isEnroute());
    assertFalse("There should not be a request to ewestfal.", document.isApprovalRequested());

    // verify that only 1 action taken has been performed
    Collection<ActionTakenValue> actionTakens =
        KEWServiceLocator.getActionTakenService().findByDocumentId(document.getDocumentId());
    assertEquals("There should be only 1 action taken.", 1, actionTakens.size());

    // now try and route the document again, an exception should be thrown
    try {
      document.route("");
      fail("A WorkflowException should have been thrown.");
    } catch (InvalidActionTakenException e) {
      e.printStackTrace();
    }

    // verify that there is still only 1 action taken (the transaction above should have rolled
    // back)
    actionTakens =
        KEWServiceLocator.getActionTakenService().findByDocumentId(document.getDocumentId());
    assertEquals("There should still be only 1 action taken.", 1, actionTakens.size());
  }
  @Test
  public void testAdhocApproversJoinScenario() throws Exception {
    WorkflowDocument document =
        WorkflowDocumentFactory.createDocument(
            getPrincipalIdForName("ewestfal"), "AdHocApproversDocType");
    document.route("");

    // should send an approve to bmcgough
    document =
        WorkflowDocumentFactory.loadDocument(
            getPrincipalIdForName("bmcgough"), document.getDocumentId());
    assertTrue("Bmcgough should have approve request.", document.isApprovalRequested());
    document.approve("");

    // at this point the document should pass the split, and end up at the WorkflowDocument2 node
    // and the AdHocApproversJoin node
    // after bypassing the AdHocJoinPoint
    Set<String> nodeNames = document.getNodeNames();
    assertEquals("There should be two node names.", 2, nodeNames.size());
    assertTrue("Should be at WorkflowDocument2 node.", nodeNames.contains("WorkflowDocument2"));
    assertTrue("Should be at WorkflowDocument2 node.", nodeNames.contains("AdHocApproversJoin"));

    // pmckown has the request at the adhoc approvers node, if we approve as him then the document
    // should _not_ transition out
    // of it's current nodes
    document =
        WorkflowDocumentFactory.loadDocument(
            getPrincipalIdForName("pmckown"), document.getDocumentId());
    assertTrue("Pmckown should have approve request.", document.isApprovalRequested());
    document.approve("");

    // the document should still be at the same nodes
    nodeNames = document.getNodeNames();
    assertEquals("There should be two node names.", 2, nodeNames.size());
    assertTrue("Should be at WorkflowDocument2 node.", nodeNames.contains("WorkflowDocument2"));
    assertTrue("Should be at WorkflowDocument2 node.", nodeNames.contains("AdHocApproversJoin"));

    // at WorkflowDocument2, rkirkend is the approver, if we approve as him we should end up at the
    // WorkflowDocumentFinal node
    document =
        WorkflowDocumentFactory.loadDocument(
            getPrincipalIdForName("rkirkend"), document.getDocumentId());
    assertTrue("Rkirkend should have approve request.", document.isApprovalRequested());
    document.approve("");

    // the document should now be at WorkflowDocumentFinal with a request to xqi
    nodeNames = document.getNodeNames();
    assertEquals("There should be one node name.", 1, nodeNames.size());
    assertTrue(
        "Should be at WorkflowDocumentFinal node.", nodeNames.contains("WorkflowDocumentFinal"));

    document =
        WorkflowDocumentFactory.loadDocument(
            getPrincipalIdForName("xqi"), document.getDocumentId());
    assertTrue("Document should still be enroute.", document.isEnroute());
    document.approve("");
    assertTrue("Document should now be final.", document.isFinal());
  }
 /**
  * @see
  *     org.kuali.rice.ken.service.NotificationWorkflowDocumentService#terminateWorkflowDocument(org.kuali.rice.kew.api.WorkflowDocument)
  */
 public void terminateWorkflowDocument(WorkflowDocument document) {
   document.superUserCancel(
       "terminating document: documentId="
           + document.getDocumentId()
           + ", appDocId="
           + document.getApplicationDocumentId());
 }
  /**
   * Test the approval of a protocol. The protocol status and its corresponding action should be set
   * to approved.
   */
  @Test
  public void runApprovedTest() throws Exception {
    ProtocolDocument protocolDocument = ProtocolFactory.createProtocolDocument();

    protocolSubmitActionService.submitToIrbForReview(
        protocolDocument.getProtocol(), getMockSubmitAction());

    documentService.routeDocument(protocolDocument, null, null);
    documentService.blanketApproveDocument(protocolDocument, null, null);
    WorkflowDocument workflowDoc = getWorkflowDocument(protocolDocument);
    WorkflowDocumentActionsService info =
        GlobalResourceLoader.getService("rice.kew.workflowDocumentActionsService");
    RoutingReportCriteria.Builder reportCriteriaBuilder =
        RoutingReportCriteria.Builder.createByDocumentId(workflowDoc.getDocumentId());
    DocumentDetail results1 = info.executeSimulation(reportCriteriaBuilder.build());

    assertTrue(workflowDoc.isFinal());

    // the status update is not happening within doRouteStatusChange anymore
    // assertEquals(protocolDocument.getProtocol().getProtocolStatusCode(),
    // ProtocolStatus.ACTIVE_OPEN_TO_ENROLLMENT);
    assertTrue(protocolDocument.getProtocol().isActive());
    // verifyProtocolAction(protocolDocument.getProtocol().getProtocolId(),
    // ProtocolActionType.APPROVED);
  }
  public ProtocolOnlineReviewDocumentBase createProtocolOnlineReviewDocument(
      ProtocolSubmissionBase protocolSubmission,
      ProtocolReviewer protocolReviewer,
      String documentDescription,
      String documentExplanation,
      String documentOrganizationDocumentNumber,
      Date dateRequested,
      Date dateDue,
      String principalId)
      throws WorkflowException {

    IacucProtocolOnlineReviewDocument protocolReviewDocument;

    Person person = personService.getPerson(principalId);
    WorkflowDocument workflowDocument =
        workflowDocumentService.createWorkflowDocument(
            IACUC_PROTOCOL_ONLINE_REVIEW_DOCUMENT_TYPE, person);

    DocumentHeader docHeader = new DocumentHeader();
    docHeader.setWorkflowDocument(workflowDocument);
    docHeader.setDocumentNumber(workflowDocument.getDocumentId().toString());
    protocolReviewDocument = new IacucProtocolOnlineReviewDocument();
    protocolReviewDocument.setDocumentNumber(docHeader.getDocumentNumber());
    protocolReviewDocument.setDocumentHeader(docHeader);

    protocolReviewDocument.getProtocolOnlineReview().setProtocol(protocolSubmission.getProtocol());

    protocolReviewDocument
        .getProtocolOnlineReview()
        .setProtocolId(protocolSubmission.getProtocolId());

    protocolReviewDocument.getProtocolOnlineReview().setProtocolSubmission(protocolSubmission);
    protocolReviewDocument
        .getProtocolOnlineReview()
        .setSubmissionIdFk(protocolSubmission.getSubmissionId());
    protocolReviewDocument
        .getProtocolOnlineReview()
        .setProtocolOnlineReviewStatusCode(IacucProtocolOnlineReviewStatus.SAVED_STATUS_CD);
    protocolReviewDocument
        .getProtocolOnlineReview()
        .setDateRequested(
            dateRequested == null ? new Date((new java.util.Date()).getTime()) : dateRequested);
    protocolReviewDocument.getProtocolOnlineReview().setDateDue(dateDue);

    protocolReviewDocument
        .getProtocolOnlineReview()
        .setProtocolReviewerId(protocolReviewer.getProtocolReviewerId());
    protocolReviewDocument.getProtocolOnlineReview().setProtocolReviewer(protocolReviewer);

    docHeader.setDocumentDescription(documentDescription);
    docHeader.setOrganizationDocumentNumber(documentOrganizationDocumentNumber);
    docHeader.setExplanation(documentExplanation);

    documentService.saveDocument(protocolReviewDocument);
    return protocolReviewDocument;
  }
  /**
   * Creates a new ProtocolReviewDocument.
   *
   * <p>Handles creating the workflow document, and the underlying ProtocolReview BO linking the
   * protocol, submission, and reviewer.
   *
   * @param protocolSubmission The protocol submission
   * @param protocolReviewerBean The bean that holds
   * @param documentDescription the description for the created document
   * @param documentExplanation the explanation for the created document
   * @param documentOrganizationNumber the organizationNumber for the created document
   * @param principalId The principalId to use when creating the workflow document. Usually this
   *     should be the principal of the user creating the review.
   * @return
   * @throws WorkflowException
   */
  protected ProtocolOnlineReviewDocumentBase createProtocolOnlineReviewDocument(
      ProtocolSubmissionBase protocolSubmission,
      ProtocolReviewer protocolReviewer,
      String documentDescription,
      String documentExplanation,
      String documentOrganizationDocumentNumber,
      Date dateRequested,
      Date dateDue,
      String principalId)
      throws WorkflowException {

    ProtocolOnlineReviewDocumentBase protocolReviewDocument;

    Person person = personService.getPerson(principalId);
    WorkflowDocument workflowDocument =
        workflowDocumentService.createWorkflowDocument(getProtocolOLRDocumentTypeHook(), person);

    DocumentHeader docHeader = new DocumentHeader();
    docHeader.setWorkflowDocument(workflowDocument);
    docHeader.setDocumentNumber(workflowDocument.getDocumentId().toString());
    protocolReviewDocument = getNewProtocolOnlineReviewDocumentInstanceHook();
    protocolReviewDocument.setDocumentNumber(docHeader.getDocumentNumber());
    protocolReviewDocument.setDocumentHeader(docHeader);

    protocolReviewDocument.getProtocolOnlineReview().setProtocol(protocolSubmission.getProtocol());

    protocolReviewDocument
        .getProtocolOnlineReview()
        .setProtocolId(protocolSubmission.getProtocolId());

    protocolReviewDocument.getProtocolOnlineReview().setProtocolSubmission(protocolSubmission);
    protocolReviewDocument
        .getProtocolOnlineReview()
        .setSubmissionIdFk(protocolSubmission.getSubmissionId());
    protocolReviewDocument
        .getProtocolOnlineReview()
        .setProtocolOnlineReviewStatusCode(getProtocolOLRSavedStatusCodeHook());
    protocolReviewDocument
        .getProtocolOnlineReview()
        .setDateRequested(
            dateRequested == null ? new Date((new java.util.Date()).getTime()) : dateRequested);
    protocolReviewDocument.getProtocolOnlineReview().setDateDue(dateDue);

    protocolReviewDocument
        .getProtocolOnlineReview()
        .setProtocolReviewerId(protocolReviewer.getProtocolReviewerId());
    protocolReviewDocument.getProtocolOnlineReview().setProtocolReviewer(protocolReviewer);

    docHeader.setDocumentDescription(documentDescription);
    docHeader.setOrganizationDocumentNumber(documentOrganizationDocumentNumber);
    docHeader.setExplanation(documentExplanation);

    documentService.saveDocument(protocolReviewDocument);
    return protocolReviewDocument;
  }
  /**
   * Tests that the document route past the join properly when there are parallel branches that
   * don't generate requests. This was coded in response to a bug found while testing with ERA in
   * order to track it down and fix it.
   */
  @Test
  public void testEmptyParallelBranches() throws Exception {

    WorkflowDocument document =
        WorkflowDocumentFactory.createDocument(
            getPrincipalIdForName("ewestfal"), PARALLEL_EMPTY_DOCUMENT_TYPE_NAME);
    document.saveDocumentData();
    assertTrue("Document should be initiated", document.isInitiated());
    assertEquals("Should be no action requests.", 0, document.getRootActionRequests().size());
    Collection<? extends Object> nodeInstances =
        KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
    assertEquals("Wrong number of active nodes.", 1, nodeInstances.size());
    document.route("");

    // should have generated a request to "bmcgough"
    document =
        WorkflowDocumentFactory.loadDocument(
            getPrincipalIdForName("bmcgough"), document.getDocumentId());
    assertTrue("Document should be enroute", document.isEnroute());
    List<ActionRequestValue> actionRequests =
        KEWServiceLocator.getActionRequestService().findPendingByDoc(document.getDocumentId());
    assertEquals("Incorrect pending action requests.", 1, actionRequests.size());
    ActionRequestValue bRequest = actionRequests.get(0);
    assertNotNull("Should have been routed through node instance.", bRequest.getNodeInstance());
    assertTrue(document.isApprovalRequested());

    document.approve("");

    // now the document should have split, passed through nodes in each branch which didn't generate
    // requests,
    // and then passed the join node and generated requests at WorkflowDocumentFinal
    document =
        WorkflowDocumentFactory.loadDocument(
            getPrincipalIdForName("xqi"), document.getDocumentId());
    assertTrue("Document should be enroute", document.isEnroute());
    assertTrue(document.isApprovalRequested());
  }
 /**
  * @see
  *     org.kuali.rice.ken.service.NotificationWorkflowDocumentService#clearAllFyisAndAcknowledgeNotificationWorkflowDocument(java.lang.String,
  *     org.kuali.rice.ken.document.kew.NotificationWorkflowDocument, java.lang.String)
  */
 public void clearAllFyisAndAcknowledgeNotificationWorkflowDocument(
     String initiatorUserId, WorkflowDocument workflowDocument, String annotation) {
   List<ActionRequest> reqs = workflowDocument.getRootActionRequests();
   for (int i = 0; i < reqs.size(); i++) {
     LOG.info("Action Request[" + i + "] = " + reqs.get(i).getActionRequested());
     if (reqs.get(i).getActionRequested().equals(ActionRequestType.ACKNOWLEDGE)) {
       workflowDocument.acknowledge(annotation);
     } else if (reqs.get(i).getActionRequested().equals(ActionRequestType.FYI)) {
       workflowDocument.logAnnotation(annotation);
       workflowDocument.fyi();
     } else {
       throw new WorkflowRuntimeException(
           "Invalid notification action request in workflow document ("
               + workflowDocument.getDocumentId()
               + ") was encountered.  Should be either an acknowledge or fyi and was not.");
     }
   }
 }
 /**
  * Tests that an exception is not thrown if you try to execute a "route" command on a document you
  * did not initiate.
  */
 @Test
 public void testRouteDefaultDocumentAsNonInitiatorUser() throws Exception {
   WorkflowDocument firstDocument =
       WorkflowDocumentFactory.createDocument(getPrincipalIdForName("user1"), DOCUMENT_TYPE_NAME);
   WorkflowDocument document =
       WorkflowDocumentFactory.loadDocument(
           getPrincipalIdForName("user2"), firstDocument.getDocumentId());
   try {
     document.route("");
     fail("Exception should have been thrown.");
   } catch (Exception e) {
     e.printStackTrace();
   }
   assertFalse("Document should not be ENROUTE.", document.isEnroute());
   //        assertFalse("There should not be a request to user2.",
   // document.isApprovalRequested());
   //
   //        // verify that there are no actions taken
   //        Collection actionTakens =
   // KEWServiceLocator.getActionTakenService().findByDocumentId(document.getDocumentId());
   //        assertEquals("There should be 0 actions taken.", 0, actionTakens.size());
 }
  /**
   * Creates a new document by document type name. The principal name passed in will be used as the
   * document initiator. If the initiatorPrincipalNm is null or blank, the current user will be
   * used.
   *
   * @see org.kuali.rice.krad.service.DocumentService#getNewDocument(String, String)
   */
  @Override
  public Document getNewDocument(String documentTypeName, String initiatorPrincipalNm)
      throws WorkflowException {

    // argument validation
    String watchName = "DocumentServiceImpl.getNewDocument";
    StopWatch watch = new StopWatch();
    watch.start();
    if (LOG.isDebugEnabled()) {
      LOG.debug(watchName + ": started");
    }
    if (StringUtils.isBlank(documentTypeName)) {
      throw new IllegalArgumentException("invalid (blank) documentTypeName");
    }
    if (GlobalVariables.getUserSession() == null) {
      throw new IllegalStateException(
          "GlobalVariables must be populated with a valid UserSession before a new document can be created");
    }

    // get the class for this docTypeName
    Class<? extends Document> documentClass = getDocumentClassByTypeName(documentTypeName);

    // get the initiator
    Person initiator = null;
    if (StringUtils.isBlank(initiatorPrincipalNm)) {
      initiator = GlobalVariables.getUserSession().getPerson();
    } else {
      initiator =
          KimApiServiceLocator.getPersonService().getPersonByPrincipalName(initiatorPrincipalNm);
      if (initiator == null) {
        initiator = GlobalVariables.getUserSession().getPerson();
      }
    }

    // get the authorization
    DocumentAuthorizer documentAuthorizer =
        getDocumentDictionaryService().getDocumentAuthorizer(documentTypeName);
    DocumentPresentationController documentPresentationController =
        getDocumentDictionaryService().getDocumentPresentationController(documentTypeName);
    // make sure this person is authorized to initiate
    if (LOG.isDebugEnabled()) {
      LOG.debug(
          "calling canInitiate from getNewDocument("
              + documentTypeName
              + ","
              + initiatorPrincipalNm
              + ")");
    }
    if (!documentPresentationController.canInitiate(documentTypeName)
        || !documentAuthorizer.canInitiate(documentTypeName, initiator)) {
      throw new DocumentAuthorizationException(
          initiator.getPrincipalName(), "initiate", documentTypeName);
    }

    // initiate new workflow entry, get the workflow doc
    WorkflowDocument workflowDocument =
        getWorkflowDocumentService().createWorkflowDocument(documentTypeName, initiator);
    UserSessionUtils.addWorkflowDocument(GlobalVariables.getUserSession(), workflowDocument);

    // create a new document header object
    DocumentHeader documentHeader = new DocumentHeader();
    documentHeader.setWorkflowDocument(workflowDocument);
    documentHeader.setDocumentNumber(workflowDocument.getDocumentId());

    // build Document of specified type
    Document document = null;
    try {
      // all maintenance documents have same class
      if (MaintenanceDocumentBase.class.isAssignableFrom(documentClass)) {
        Class<?>[] defaultConstructor = new Class[] {String.class};
        Constructor<? extends Document> cons = documentClass.getConstructor(defaultConstructor);
        if (cons == null) {
          throw new ConfigurationException(
              "Could not find constructor with document type name parameter needed for Maintenance Document Base class");
        }
        document = cons.newInstance(documentTypeName);
      } else {
        // non-maintenance document
        document = documentClass.newInstance();
      }
    } catch (IllegalAccessException e) {
      throw new RuntimeException("Error instantiating Document", e);
    } catch (InstantiationException e) {
      throw new RuntimeException("Error instantiating Document", e);
    } catch (SecurityException e) {
      throw new RuntimeException("Error instantiating Maintenance Document", e);
    } catch (NoSuchMethodException e) {
      throw new RuntimeException(
          "Error instantiating Maintenance Document: No constructor with String parameter found",
          e);
    } catch (IllegalArgumentException e) {
      throw new RuntimeException("Error instantiating Maintenance Document", e);
    } catch (InvocationTargetException e) {
      throw new RuntimeException("Error instantiating Maintenance Document", e);
    }

    document.setDocumentHeader(documentHeader);
    document.setDocumentNumber(documentHeader.getDocumentNumber());

    watch.stop();
    if (LOG.isDebugEnabled()) {
      LOG.debug(watchName + ": " + watch.toString());
    }

    return document;
  }
  @Test
  public void testParallelRoute() throws Exception {
    WorkflowDocument document =
        WorkflowDocumentFactory.createDocument(
            getPrincipalIdForName("ewestfal"), DOCUMENT_TYPE_NAME);
    document.saveDocumentData();
    assertTrue("Document should be initiated", document.isInitiated());
    assertEquals("Should be no action requests.", 0, document.getRootActionRequests().size());
    Collection<RouteNodeInstance> nodeInstances =
        KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
    assertEquals("Wrong number of active nodes.", 1, nodeInstances.size());
    document.route("Routing for parallel");

    // should have generated a request to "bmcgough"
    document =
        WorkflowDocumentFactory.loadDocument(
            getPrincipalIdForName("bmcgough"), document.getDocumentId());
    assertTrue("Document should be enroute", document.isEnroute());
    List<ActionRequestValue> actionRequests =
        KEWServiceLocator.getActionRequestService().findPendingByDoc(document.getDocumentId());
    assertEquals("Incorrect pending action requests.", 1, actionRequests.size());
    ActionRequestValue bRequest = actionRequests.get(0);
    assertNotNull("Should have been routed through node instance.", bRequest.getNodeInstance());
    assertTrue(document.isApprovalRequested());

    document.approve("Approving test");

    // document should split at this point and generate an ack to temay and approves to rkirkend and
    // pmckown
    document =
        WorkflowDocumentFactory.loadDocument(
            getPrincipalIdForName("rkirkend"), document.getDocumentId());
    assertTrue("Document should be enroute", document.isEnroute());
    actionRequests =
        KEWServiceLocator.getActionRequestService().findPendingByDoc(document.getDocumentId());
    assertEquals("Incorrect pending action requests.", 3, actionRequests.size());
    boolean isToTemay = false;
    boolean isToPmckown = false;
    boolean isToRkirkend = false;
    for (Iterator iterator = actionRequests.iterator(); iterator.hasNext(); ) {
      ActionRequestValue actionRequest = (ActionRequestValue) iterator.next();
      if (actionRequest.getPrincipalId().equals(getPrincipalIdForName("temay"))) {
        isToTemay = true;
        assertEquals(
            "Request should be activated.",
            ActionRequestStatus.ACTIVATED.getCode(),
            actionRequest.getStatus());
        assertEquals(
            "Wrong action requested.",
            KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ,
            actionRequest.getActionRequested());
        assertNotNull(
            "Should have been routed through node instance.", actionRequest.getNodeInstance());
        assertEquals(
            "Invalid node.",
            ACKNOWLEDGE_1_NODE,
            actionRequest.getNodeInstance().getRouteNode().getRouteNodeName());
      }
      if (actionRequest.getPrincipalId().equals(getPrincipalIdForName("rkirkend"))) {
        isToRkirkend = true;
        assertEquals(
            "Request should be activated.",
            ActionRequestStatus.ACTIVATED.getCode(),
            actionRequest.getStatus());
        assertEquals(
            "Wrong action requested.",
            KewApiConstants.ACTION_REQUEST_APPROVE_REQ,
            actionRequest.getActionRequested());
        assertNotNull(
            "Should have been routed through node instance.", actionRequest.getNodeInstance());
        assertEquals(
            "Invalid node.",
            WORKFLOW_DOCUMENT_2_NODE,
            actionRequest.getNodeInstance().getRouteNode().getRouteNodeName());
      }
      if (actionRequest.getPrincipalId().equals(getPrincipalIdForName("pmckown"))) {
        isToPmckown = true;
        assertEquals(
            "Request should be activated.",
            ActionRequestStatus.ACTIVATED.getCode(),
            actionRequest.getStatus());
        assertEquals(
            "Wrong action requested.",
            KewApiConstants.ACTION_REQUEST_APPROVE_REQ,
            actionRequest.getActionRequested());
        assertNotNull(
            "Should have been routed through node instance.", actionRequest.getNodeInstance());
        assertEquals(
            "Invalid node.",
            WORKFLOW_DOCUMENT_3_NODE,
            actionRequest.getNodeInstance().getRouteNode().getRouteNodeName());
      }
    }
    assertTrue("No request to temay.", isToTemay);
    assertTrue("No request to pmckown.", isToPmckown);
    assertTrue("No request to rkirkend.", isToRkirkend);

    // check that we are at both nodes, one in each branch
    Set<String> nodeNames = document.getNodeNames();
    assertEquals("Wrong number of node names.", 2, nodeNames.size());
    boolean isNode2 = false;
    boolean isNode3 = false;
    for (String name : nodeNames) {
      if (name.equals(WORKFLOW_DOCUMENT_2_NODE)) {
        isNode2 = true;
      }
      if (name.equals(WORKFLOW_DOCUMENT_3_NODE)) {
        isNode3 = true;
      }
    }
    assertTrue("Not at node2.", isNode2);
    assertTrue("Not at node3.", isNode3);
    nodeInstances =
        KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
    assertEquals("Wrong number of active nodes.", 2, nodeInstances.size());
    Iterator<RouteNodeInstance> iterator = nodeInstances.iterator();
    RouteNodeInstance instance1 = (RouteNodeInstance) iterator.next();
    RouteNodeInstance instance2 = (RouteNodeInstance) iterator.next();
    assertNotNull("Node should be in branch.", instance1.getBranch());
    assertNotNull("Node should be in branch.", instance2.getBranch());
    assertTrue(
        "Branches should be different.",
        !instance1.getBranch().getBranchId().equals(instance2.getBranch().getBranchId()));

    document =
        WorkflowDocumentFactory.loadDocument(
            getPrincipalIdForName("rkirkend"), document.getDocumentId());
    assertTrue("Should have request.", document.isApprovalRequested());
    document.approve("Git-r-dun");

    nodeInstances =
        KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
    assertEquals("Wrong number of active nodes.", 2, nodeInstances.size());
    boolean isAtJoin = false;
    boolean isAtWD3 = false;
    for (RouteNodeInstance nodeInstance : nodeInstances) {
      if (nodeInstance.getRouteNode().getRouteNodeName().equals(JOIN_NODE)) {
        assertEquals(
            "Join branch should be split branch.",
            instance1.getBranch().getParentBranch().getBranchId(),
            nodeInstance.getBranch().getBranchId());
        isAtJoin = true;
      }
      if (nodeInstance.getRouteNode().getRouteNodeName().equals(WORKFLOW_DOCUMENT_3_NODE)) {
        isAtWD3 = true;
      }
    }
    assertTrue("Not at join", isAtJoin);
    assertTrue("Not at WD3", isAtWD3);

    document =
        WorkflowDocumentFactory.loadDocument(
            getPrincipalIdForName("pmckown"), document.getDocumentId());
    assertTrue("Should have request.", document.isApprovalRequested());
    document.approve("Do it.");

    nodeInstances =
        KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
    assertEquals("Wrong number of active nodes.", 1, nodeInstances.size());
    boolean isAtWDF = false;
    for (RouteNodeInstance nodeInstance : nodeInstances) {
      if (nodeInstance.getRouteNode().getRouteNodeName().equals(WORKFLOW_DOCUMENT_FINAL_NODE)) {
        isAtWDF = true;
      }
    }
    assertTrue("Not at WDF", isAtWDF);

    document =
        WorkflowDocumentFactory.loadDocument(
            getPrincipalIdForName("xqi"), document.getDocumentId());
    assertTrue("Should still be enroute.", document.isEnroute());
    assertTrue("Should have request.", document.isApprovalRequested());
    document.approve("I'm the last approver");

    assertTrue("Document should be processed.", document.isProcessed());
    nodeInstances =
        KEWServiceLocator.getRouteNodeService().getActiveNodeInstances(document.getDocumentId());
    assertEquals(
        "The doc is processed so no node instances should be active", 0, nodeInstances.size());

    document =
        WorkflowDocumentFactory.loadDocument(
            getPrincipalIdForName("temay"), document.getDocumentId());
    assertTrue("Should have request.", document.isAcknowledgeRequested());
    document.acknowledge("");
    assertTrue(document.isFinal());
  }
  /**
   * Implements by instantiating a NotificationWorkflowDocument, which in turn interacts with
   * Workflow to set it up with an initiator of the passed in user id.
   *
   * @see
   *     org.kuali.rice.ken.service.NotificationWorkflowDocumentService#createAndAdHocRouteNotificationWorkflowDocument(org.kuali.rice.ken.bo.NotificationMessageDelivery,
   *     java.lang.String, java.lang.String, java.lang.String)
   */
  public String createAndAdHocRouteNotificationWorkflowDocument(
      NotificationMessageDelivery messageDelivery,
      String initiatorUserId,
      String recipientUserId,
      String annotation) {
    // obtain a workflow user object first
    // WorkflowIdDTO initiator = new WorkflowIdDTO(initiatorUserId);

    // now construct the workflow document, which will interact with workflow
    WorkflowDocument document;
    if (StringUtils.isNotBlank(messageDelivery.getNotification().getDocTypeName())) {
      document =
          NotificationWorkflowDocument.createNotificationDocument(
              initiatorUserId, messageDelivery.getNotification().getDocTypeName());
    } else {
      document = NotificationWorkflowDocument.createNotificationDocument(initiatorUserId);
    }

    // this is our loose foreign key to our message delivery record in notification
    document.setApplicationDocumentId(messageDelivery.getId().toString());
    // document.setAppDocId(messageDelivery.getId().toString());

    // now add the content of the notification as XML to the document
    document.setApplicationContent(
        messageContentService.generateNotificationMessage(
            messageDelivery.getNotification(), messageDelivery.getUserRecipientId()));

    if (!StringUtils.isBlank(messageDelivery.getNotification().getTitle())) {
      document.setTitle(messageDelivery.getNotification().getTitle());
    } else {
      LOG.error(
          "Encountered notification with no title set: Message Delivery #"
              + messageDelivery.getId()
              + ", Notification #"
              + messageDelivery.getNotification().getId());
    }

    // now set up the ad hoc route
    String actionRequested;
    if (NotificationConstants.DELIVERY_TYPES.ACK.equals(
        messageDelivery.getNotification().getDeliveryType())) {
      actionRequested = NotificationConstants.KEW_CONSTANTS.ACK_AD_HOC_ROUTE;
    } else {
      actionRequested = NotificationConstants.KEW_CONSTANTS.FYI_AD_HOC_ROUTE;
    }

    // Clarification of ad hoc route call
    // param 1 - actionRequested will be either ACK or FYI
    // param 2 - annotation is whatever text we pass in to describe the transaction - this will be
    // system generated
    // param 3 - recipient is the person who will receive this request
    // param 4 - this is the responsibilityParty (a.k.a the system that produced this request), so
    // we'll put the producer name in there
    // param 5 - this is the "force action" requests - if set to true, this will be delivered to the
    // recipients list regardless of
    //           whether the recipient has already taken action on this request; in our case, this
    // doesn't really apply at this point in time,
    //           so we'll set to true just to be safe

    // recipientUserId will always be a principal ID due to code changes in
    // NotificationMessageDeliveryResolverServiceImpl.buildCompleteRecipientList()
    Principal principal = KimApiServiceLocator.getIdentityService().getPrincipal(recipientUserId);

    document.adHocToPrincipal(
        ActionRequestType.fromCode(actionRequested),
        annotation,
        principal.getPrincipalId(),
        messageDelivery.getNotification().getProducer().getName(),
        true);

    // now actually route it along its way
    document.route(annotation);

    return document.getDocumentId();
  }