/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.module.purap.document.service.impl;
import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument;
import org.kuali.kfs.module.purap.document.service.PurApWorkflowIntegrationService;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.rice.kew.api.KewApiConstants;
import org.kuali.rice.kew.api.KewApiServiceLocator;
import org.kuali.rice.kew.api.WorkflowDocument;
import org.kuali.rice.kew.api.action.ActionRequest;
import org.kuali.rice.kew.api.action.RoutingReportCriteria;
import org.kuali.rice.kew.api.action.WorkflowDocumentActionsService;
import org.kuali.rice.kew.api.document.node.RouteNodeInstance;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kim.api.identity.Person;
import org.kuali.rice.kim.api.identity.PersonService;
import org.kuali.rice.krad.document.Document;
import org.kuali.rice.krad.util.GlobalVariables;
import org.kuali.rice.krad.util.ObjectUtils;
import org.kuali.rice.krad.workflow.service.WorkflowDocumentService;
import org.springframework.transaction.annotation.Transactional;
/**
* This class holds methods for Purchasing and Accounts Payable documents to integrate with workflow services and operations.
*/
@Transactional
public class PurApWorkflowIntegrationServiceImpl implements PurApWorkflowIntegrationService {
private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurApWorkflowIntegrationServiceImpl.class);
private WorkflowDocumentService workflowDocumentService;
private PersonService personService;
public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
this.workflowDocumentService = workflowDocumentService;
}
/**
* Performs a super user approval of all action requests.
*
* @param superUser
* @param documentNumber
* @param nodeName
* @param user
* @param annotation
* @throws WorkflowException
*/
protected void superUserApproveAllActionRequests(Person superUser, String documentNumber, String nodeName, Person user, String annotation) throws WorkflowException {
WorkflowDocument workflowDoc = workflowDocumentService.loadWorkflowDocument(documentNumber, superUser);
List<ActionRequest> actionRequests = getActiveActionRequestsForCriteria(documentNumber, nodeName, user);
for (ActionRequest actionRequestDTO : actionRequests) {
if (LOG.isDebugEnabled()) {
LOG.debug("Active Action Request list size to process is " + actionRequests.size());
LOG.debug("Attempting to super user approve action request with id " + actionRequestDTO.getId());
}
SpringContext.getBean(org.kuali.rice.kew.routeheader.service.WorkflowDocumentService.class).superUserActionRequestApproveAction(superUser.getPrincipalId(), documentNumber, actionRequestDTO.getId(), annotation, true );
break;
}
}
/**
* @see org.kuali.kfs.module.purap.document.service.PurApWorkflowIntegrationService#takeAllActionsForGivenCriteria(org.kuali.rice.krad.document.Document,
* java.lang.String, java.lang.String, org.kuali.rice.kim.api.identity.Person, java.lang.String)
*/
@Override
public boolean takeAllActionsForGivenCriteria(Document document, String potentialAnnotation, String nodeName, Person userToCheck, String superUserNetworkId) {
try {
String documentNumber = document.getDocumentNumber();
String networkIdString = (ObjectUtils.isNotNull(userToCheck)) ? userToCheck.getPrincipalName() : "none";
List<ActionRequest> activeActionRequests = getActiveActionRequestsForCriteria(documentNumber, nodeName, userToCheck);
// if no action requests are found... no actions required
if (activeActionRequests.isEmpty()) {
if (LOG.isDebugEnabled()) {
LOG.debug("No action requests found on document id " + documentNumber + " for given criteria: principalName - " + networkIdString + "; nodeName - " + nodeName);
}
return false;
}
// if a super user network id was given... take all actions as super user
if (StringUtils.isNotBlank(superUserNetworkId)) {
// approve each action request as the super user
Person superUser = getPersonService().getPersonByPrincipalName(superUserNetworkId);
if (LOG.isDebugEnabled()) {
LOG.debug("Attempting to super user approve all action requests found on document id " + documentNumber + " for given criteria: principalName - " + networkIdString + "; nodeName - " + nodeName);
}
superUserApproveAllActionRequests(superUser, documentNumber, nodeName, userToCheck, potentialAnnotation);
return true;
}
else {
// if a user was given... take the action as that user
if (ObjectUtils.isNotNull(userToCheck)) {
WorkflowDocument workflowDocument = workflowDocumentService.loadWorkflowDocument(documentNumber, userToCheck);
boolean containsFyiRequest = false;
boolean containsAckRequest = false;
boolean containsApproveRequest = false;
boolean containsCompleteRequest = false;
if (StringUtils.isBlank(nodeName)) {
// requests are for a specific user but not at a specific level... take regular actions
containsCompleteRequest = workflowDocument.isCompletionRequested();
containsApproveRequest = workflowDocument.isApprovalRequested();
containsAckRequest = workflowDocument.isAcknowledgeRequested();
containsFyiRequest = workflowDocument.isFYIRequested();
}
else {
for (ActionRequest actionRequestDTO : activeActionRequests) {
containsFyiRequest |= (KewApiConstants.ACTION_REQUEST_FYI_REQ.equals(actionRequestDTO.getActionRequested().getCode()));
containsAckRequest |= (KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ.equals(actionRequestDTO.getActionRequested().getCode()));
containsApproveRequest |= (KewApiConstants.ACTION_REQUEST_APPROVE_REQ.equals(actionRequestDTO.getActionRequested().getCode()));
containsCompleteRequest |= (KewApiConstants.ACTION_REQUEST_COMPLETE_REQ.equals(actionRequestDTO.getActionRequested().getCode()));
}
}
if (containsCompleteRequest || containsApproveRequest) {
workflowDocumentService.approve(workflowDocument, potentialAnnotation, new ArrayList());
return true;
}
else if (containsAckRequest) {
workflowDocumentService.acknowledge(workflowDocument, potentialAnnotation, new ArrayList());
return true;
}
else if (containsFyiRequest) {
workflowDocumentService.clearFyi(workflowDocument, new ArrayList());
return true;
}
}
else {
// no user to check and no super user given... cannot take actions on document
String errorMessage = "No super user network id and no user to check given. Need at least one or both";
LOG.error(errorMessage);
throw new RuntimeException(errorMessage);
}
}
return false;
}
catch (WorkflowException e) {
String errorMessage = "Error trying to get action requests of document id '" + document.getDocumentNumber() + "'";
LOG.error("takeAllActionsForGivenCriteria() " + errorMessage, e);
throw new RuntimeException(errorMessage, e);
}
catch (Exception e) {
String errorMessage = "Error trying to get user for network id '" + superUserNetworkId + "'";
LOG.error("takeAllActionsForGivenCriteria() " + errorMessage, e);
throw new RuntimeException(errorMessage, e);
}
}
/**
* Retrieves the active action requests for the given criteria
*
* @param documentNumber
* @param nodeName
* @param user
* @return List of action requests
* @throws WorkflowException
*/
protected List<ActionRequest> getActiveActionRequestsForCriteria(String documentNumber, String nodeName, Person user) throws WorkflowException {
if ( StringUtils.isBlank(documentNumber) ) {
// throw exception
}
org.kuali.rice.kew.api.document.WorkflowDocumentService workflowDocService = KewApiServiceLocator.getWorkflowDocumentService();
List<ActionRequest> actionRequests = workflowDocService.getActionRequestsForPrincipalAtNode(documentNumber, nodeName, user.getPrincipalId());
List<ActionRequest> activeRequests = new ArrayList<ActionRequest>();
for (ActionRequest actionRequest : actionRequests) {
// identify which requests for the given node name can be satisfied by an action by this user
if (actionRequest.isActivated()) {
activeRequests.add(actionRequest);
}
}
return activeRequests;
}
/**
* DON'T CALL THIS IF THE DOC HAS NOT BEEN SAVED
*
* @see org.kuali.kfs.module.purap.document.service.PurApWorkflowIntegrationService#willDocumentStopAtGivenFutureRouteNode(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument,
* org.kuali.kfs.module.purap.PurapWorkflowConstants.NodeDetails)
*/
@Override
public boolean willDocumentStopAtGivenFutureRouteNode(PurchasingAccountsPayableDocument document, String givenNodeName) {
if (givenNodeName == null) {
throw new InvalidParameterException("Given Node Detail object was null");
}
try {
String activeNode = null;
Set<String> currentNodes = document.getDocumentHeader().getWorkflowDocument().getCurrentNodeNames();
if (CollectionUtils.isNotEmpty(currentNodes)) {
String[] nodeNames = currentNodes.toArray(new String[0]);
if (nodeNames.length == 1) {
activeNode = nodeNames[0];
}
}
if (isGivenNodeAfterCurrentNode(document, activeNode, givenNodeName)) {
if (document.getDocumentHeader().getWorkflowDocument().isInitiated()) {
// document is only initiated so we need to pass xml for workflow to simulate route properly
RoutingReportCriteria.Builder builder = RoutingReportCriteria.Builder.createByDocumentTypeName(document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
builder.setXmlContent(document.getXmlForRouteReport());
builder.setRoutingPrincipalId(GlobalVariables.getUserSession().getPerson().getPrincipalId());
builder.setTargetNodeName(givenNodeName);
RoutingReportCriteria reportCriteria = builder.build();
boolean value = SpringContext.getBean(WorkflowDocumentActionsService.class).documentWillHaveAtLeastOneActionRequest(reportCriteria, Arrays.asList( KewApiConstants.ACTION_REQUEST_APPROVE_REQ, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ ), false);
return value;
}else {
/* Document has had at least one workflow action taken so we need to pass the doc id so the simulation will use
* the existing actions taken and action requests in determining if rules will fire or not. We also need to call
* a save routing data so that the xml Workflow uses represents what is currently on the document
*/
RoutingReportCriteria.Builder builder = RoutingReportCriteria.Builder.createByDocumentId(document.getDocumentNumber());
builder.setXmlContent(document.getXmlForRouteReport());
builder.setTargetNodeName(givenNodeName);
RoutingReportCriteria reportCriteria = builder.build();
boolean value = SpringContext.getBean(WorkflowDocumentActionsService.class).documentWillHaveAtLeastOneActionRequest(reportCriteria, Arrays.asList( KewApiConstants.ACTION_REQUEST_APPROVE_REQ, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ ), false);
return value;
}
}
return false;
}
catch (Exception e) {
String errorMessage = "Error trying to test document id '" + document.getDocumentNumber() + "' for action requests at node name '" + givenNodeName + "'";
LOG.error("isDocumentStoppingAtRouteLevel() " + errorMessage, e);
throw new RuntimeException(errorMessage, e);
}
}
/**
* Evaluates if given node is after the current node
*
* @param currentNodeDetail
* @param givenNodeDetail
* @return boolean to indicate if given node is after the current node
*/
protected boolean isGivenNodeAfterCurrentNode(Document document, String currentNodeName, String givenNodeName) {
if (ObjectUtils.isNull(givenNodeName)) {
// given node does not exist
return false;
}
if (ObjectUtils.isNull(currentNodeName)) {
// current node does not exist... assume we are pre-route
return true;
}
List<RouteNodeInstance> routeNodes = KewApiServiceLocator.getWorkflowDocumentService().getRouteNodeInstances(document.getDocumentNumber());
int currentNodeIndex = 0;
int givenNodeIndex = 0;
RouteNodeInstance node = null;
//find index of given and current node
for(int i=0; i < routeNodes.size(); i++){
node = routeNodes.get(i);
if(node.getName().equals(currentNodeName)){
currentNodeIndex = i;
}
if(node.getName().equals(givenNodeName)){
givenNodeIndex = i;
}
}
//compare
return givenNodeIndex > currentNodeIndex;
}
/**
* @return Returns the personService.
*/
protected PersonService getPersonService() {
if(personService==null) {
personService = SpringContext.getBean(PersonService.class);
}
return personService;
}
}