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