View Javadoc
1   /*
2    * Copyright 2007 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.ole.module.purap.document.service.impl;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument;
20  import org.kuali.ole.module.purap.document.service.PurApWorkflowIntegrationService;
21  import org.kuali.ole.sys.context.SpringContext;
22  import org.kuali.rice.kew.api.KewApiConstants;
23  import org.kuali.rice.kew.api.KewApiServiceLocator;
24  import org.kuali.rice.kew.api.WorkflowDocument;
25  import org.kuali.rice.kew.api.action.ActionRequest;
26  import org.kuali.rice.kew.api.action.RoutingReportCriteria;
27  import org.kuali.rice.kew.api.action.WorkflowDocumentActionsService;
28  import org.kuali.rice.kew.api.document.node.RouteNodeInstance;
29  import org.kuali.rice.kew.api.exception.WorkflowException;
30  import org.kuali.rice.kim.api.identity.Person;
31  import org.kuali.rice.kim.api.identity.PersonService;
32  import org.kuali.rice.krad.document.Document;
33  import org.kuali.rice.krad.util.GlobalVariables;
34  import org.kuali.rice.krad.util.ObjectUtils;
35  import org.kuali.rice.krad.workflow.service.WorkflowDocumentService;
36  import org.springframework.transaction.annotation.Transactional;
37  
38  import java.security.InvalidParameterException;
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.List;
42  
43  /**
44   * This class holds methods for Purchasing and Accounts Payable documents to integrate with workflow services and operations.
45   */
46  @Transactional
47  public class PurApWorkflowIntegrationServiceImpl implements PurApWorkflowIntegrationService {
48      private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurApWorkflowIntegrationServiceImpl.class);
49  
50  
51      private WorkflowDocumentService workflowDocumentService;
52      private PersonService personService;
53  
54  
55      public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
56          this.workflowDocumentService = workflowDocumentService;
57      }
58  
59      /**
60       * Performs a super user approval of all action requests.
61       *
62       * @param superUser
63       * @param documentNumber
64       * @param nodeName
65       * @param user
66       * @param annotation
67       * @throws WorkflowException
68       */
69      protected void superUserApproveAllActionRequests(Person superUser, String documentNumber, String nodeName, Person user, String annotation) throws WorkflowException {
70          WorkflowDocument workflowDoc = workflowDocumentService.loadWorkflowDocument(documentNumber, superUser);
71          List<ActionRequest> actionRequests = getActiveActionRequestsForCriteria(documentNumber, nodeName, user);
72          for (ActionRequest actionRequestDTO : actionRequests) {
73              if (LOG.isDebugEnabled()) {
74                  LOG.debug("Active Action Request list size to process is " + actionRequests.size());
75                  LOG.debug("Attempting to super user approve action request with id " + actionRequestDTO.getId());
76              }
77              SpringContext.getBean(org.kuali.rice.kew.routeheader.service.WorkflowDocumentService.class).superUserActionRequestApproveAction(superUser.getPrincipalId(), documentNumber, actionRequestDTO.getId(), annotation, true);
78              break;
79          }
80      }
81  
82      /**
83       * @see org.kuali.ole.module.purap.document.service.PurApWorkflowIntegrationService#takeAllActionsForGivenCriteria(org.kuali.rice.krad.document.Document,
84       *      java.lang.String, java.lang.String, org.kuali.rice.kim.api.identity.Person, java.lang.String)
85       */
86      @Override
87      public boolean takeAllActionsForGivenCriteria(Document document, String potentialAnnotation, String nodeName, Person userToCheck, String superUserNetworkId) {
88          try {
89              String documentNumber = document.getDocumentNumber();
90              String networkIdString = (ObjectUtils.isNotNull(userToCheck)) ? userToCheck.getPrincipalName() : "none";
91              List<ActionRequest> activeActionRequests = getActiveActionRequestsForCriteria(documentNumber, nodeName, userToCheck);
92  
93              // if no action requests are found... no actions required
94              if (activeActionRequests.isEmpty()) {
95                  if (LOG.isDebugEnabled()) {
96                      LOG.debug("No action requests found on document id " + documentNumber + " for given criteria:  principalName - " + networkIdString + "; nodeName - " + nodeName);
97                  }
98                  return false;
99              }
100 
101             // if a super user network id was given... take all actions as super user
102             if (StringUtils.isNotBlank(superUserNetworkId)) {
103                 // approve each action request as the super user
104                 Person superUser = getPersonService().getPersonByPrincipalName(superUserNetworkId);
105                 if (LOG.isDebugEnabled()) {
106                     LOG.debug("Attempting to super user approve all action requests found on document id " + documentNumber + " for given criteria:  principalName - " + networkIdString + "; nodeName - " + nodeName);
107                 }
108                 superUserApproveAllActionRequests(superUser, documentNumber, nodeName, userToCheck, potentialAnnotation);
109                 return true;
110             } else {
111                 // if a user was given... take the action as that user
112                 if (ObjectUtils.isNotNull(userToCheck)) {
113                     WorkflowDocument workflowDocument = workflowDocumentService.loadWorkflowDocument(documentNumber, userToCheck);
114                     boolean containsFyiRequest = false;
115                     boolean containsAckRequest = false;
116                     boolean containsApproveRequest = false;
117                     boolean containsCompleteRequest = false;
118                     if (StringUtils.isBlank(nodeName)) {
119                         // requests are for a specific user but not at a specific level... take regular actions
120                         containsCompleteRequest = workflowDocument.isCompletionRequested();
121                         containsApproveRequest = workflowDocument.isApprovalRequested();
122                         containsAckRequest = workflowDocument.isAcknowledgeRequested();
123                         containsFyiRequest = workflowDocument.isFYIRequested();
124                     } else {
125                         for (ActionRequest actionRequestDTO : activeActionRequests) {
126                             containsFyiRequest |= (KewApiConstants.ACTION_REQUEST_FYI_REQ.equals(actionRequestDTO.getActionRequested().getCode()));
127                             containsAckRequest |= (KewApiConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ.equals(actionRequestDTO.getActionRequested().getCode()));
128                             containsApproveRequest |= (KewApiConstants.ACTION_REQUEST_APPROVE_REQ.equals(actionRequestDTO.getActionRequested().getCode()));
129                             containsCompleteRequest |= (KewApiConstants.ACTION_REQUEST_COMPLETE_REQ.equals(actionRequestDTO.getActionRequested().getCode()));
130                         }
131                     }
132                     if (containsCompleteRequest || containsApproveRequest) {
133                         workflowDocumentService.approve(workflowDocument, potentialAnnotation, new ArrayList());
134                         return true;
135                     } else if (containsAckRequest) {
136                         workflowDocumentService.acknowledge(workflowDocument, potentialAnnotation, new ArrayList());
137                         return true;
138                     } else if (containsFyiRequest) {
139                         workflowDocumentService.clearFyi(workflowDocument, new ArrayList());
140                         return true;
141                     }
142                 } else {
143                     // no user to check and no super user given... cannot take actions on document
144                     String errorMessage = "No super user network id and no user to check given.  Need at least one or both";
145                     LOG.error(errorMessage);
146                     throw new RuntimeException(errorMessage);
147                 }
148             }
149             return false;
150         } catch (WorkflowException e) {
151             String errorMessage = "Error trying to get action requests of document id '" + document.getDocumentNumber() + "'";
152             LOG.error("takeAllActionsForGivenCriteria() " + errorMessage, e);
153             throw new RuntimeException(errorMessage, e);
154         } catch (Exception e) {
155             String errorMessage = "Error trying to get user for network id '" + superUserNetworkId + "'";
156             LOG.error("takeAllActionsForGivenCriteria() " + errorMessage, e);
157             throw new RuntimeException(errorMessage, e);
158         }
159     }
160 
161     /**
162      * Retrieves the active action requests for the given criteria
163      *
164      * @param documentNumber
165      * @param nodeName
166      * @param user
167      * @return List of action requests
168      * @throws WorkflowException
169      */
170     protected List<ActionRequest> getActiveActionRequestsForCriteria(String documentNumber, String nodeName, Person user) throws WorkflowException {
171         if (StringUtils.isBlank(documentNumber)) {
172             // throw exception
173         }
174         org.kuali.rice.kew.api.document.WorkflowDocumentService workflowDocService = KewApiServiceLocator.getWorkflowDocumentService();
175         List<ActionRequest> actionRequests = workflowDocService.getActionRequestsForPrincipalAtNode(documentNumber, nodeName, user.getPrincipalId());
176         List<ActionRequest> activeRequests = new ArrayList<ActionRequest>();
177         for (ActionRequest actionRequest : actionRequests) {
178             // identify which requests for the given node name can be satisfied by an action by this user
179             if (actionRequest.isActivated()) {
180                 activeRequests.add(actionRequest);
181             }
182         }
183         return activeRequests;
184     }
185 
186     /**
187      * DON'T CALL THIS IF THE DOC HAS NOT BEEN SAVED
188      *
189      * @see org.kuali.ole.module.purap.document.service.PurApWorkflowIntegrationService#willDocumentStopAtGivenFutureRouteNode(org.kuali.ole.module.purap.document.PurchasingAccountsPayableDocument,
190      *      org.kuali.ole.module.purap.PurapWorkflowConstants.NodeDetails)
191      */
192     @Override
193     public boolean willDocumentStopAtGivenFutureRouteNode(PurchasingAccountsPayableDocument document, String givenNodeName) {
194         if (givenNodeName == null) {
195             throw new InvalidParameterException("Given Node Detail object was null");
196         }
197         try {
198             String activeNode = null;
199             String[] nodeNames = document.getDocumentHeader().getWorkflowDocument().getCurrentNodeNames().toArray(new String[0]);
200 
201             if (nodeNames.length == 1) {
202                 activeNode = nodeNames[0];
203             }
204 
205             if (isGivenNodeAfterCurrentNode(document, activeNode, givenNodeName)) {
206                 if (document.getDocumentHeader().getWorkflowDocument().isInitiated()) {
207                     // document is only initiated so we need to pass xml for workflow to simulate route properly
208                     RoutingReportCriteria.Builder builder = RoutingReportCriteria.Builder.createByDocumentTypeName(document.getDocumentHeader().getWorkflowDocument().getDocumentTypeName());
209                     builder.setXmlContent(document.getXmlForRouteReport());
210                     builder.setRoutingPrincipalId(GlobalVariables.getUserSession().getPerson().getPrincipalId());
211                     builder.setTargetNodeName(givenNodeName);
212                     RoutingReportCriteria reportCriteria = builder.build();
213                     boolean value = SpringContext.getBean(WorkflowDocumentActionsService.class).documentWillHaveAtLeastOneActionRequest(reportCriteria, Arrays.asList(KewApiConstants.ACTION_REQUEST_APPROVE_REQ, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ), false);
214                     return value;
215                 } else {
216                     /* Document has had at least one workflow action taken so we need to pass the doc id so the simulation will use
217                      * the existing actions taken and action requests in determining if rules will fire or not. We also need to call
218                      * a save routing data so that the xml Workflow uses represents what is currently on the document
219                      */
220                     RoutingReportCriteria.Builder builder = RoutingReportCriteria.Builder.createByDocumentId(document.getDocumentNumber());
221                     builder.setXmlContent(document.getXmlForRouteReport());
222                     builder.setTargetNodeName(givenNodeName);
223                     RoutingReportCriteria reportCriteria = builder.build();
224                     boolean value = SpringContext.getBean(WorkflowDocumentActionsService.class).documentWillHaveAtLeastOneActionRequest(reportCriteria, Arrays.asList(KewApiConstants.ACTION_REQUEST_APPROVE_REQ, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ), false);
225                     return value;
226                 }
227             }
228             return false;
229         } catch (Exception e) {
230             String errorMessage = "Error trying to test document id '" + document.getDocumentNumber() + "' for action requests at node name '" + givenNodeName + "'";
231             LOG.error("isDocumentStoppingAtRouteLevel() " + errorMessage, e);
232             throw new RuntimeException(errorMessage, e);
233         }
234     }
235 
236     /**
237      * Evaluates if given node is after the current node
238      *
239      * @param currentNodeDetail
240      * @param givenNodeDetail
241      * @return boolean to indicate if given node is after the current node
242      */
243     protected boolean isGivenNodeAfterCurrentNode(Document document, String currentNodeName, String givenNodeName) {
244         if (ObjectUtils.isNull(givenNodeName)) {
245             // given node does not exist
246             return false;
247         }
248         if (ObjectUtils.isNull(currentNodeName)) {
249             // current node does not exist... assume we are pre-route
250             return true;
251         }
252 
253         List<RouteNodeInstance> routeNodes = KewApiServiceLocator.getWorkflowDocumentService().getRouteNodeInstances(document.getDocumentNumber());
254 
255         int currentNodeIndex = 0;
256         int givenNodeIndex = 0;
257         RouteNodeInstance node = null;
258 
259         //find index of given and current node
260         for (int i = 0; i < routeNodes.size(); i++) {
261             node = routeNodes.get(i);
262 
263             if (node.getName().equals(currentNodeName)) {
264                 currentNodeIndex = i;
265             }
266             if (node.getName().equals(givenNodeName)) {
267                 givenNodeIndex = i;
268             }
269         }
270 
271         //compare
272         return givenNodeIndex > currentNodeIndex;
273 
274     }
275 
276 
277     /**
278      * @return Returns the personService.
279      */
280     protected PersonService getPersonService() {
281         if (personService == null) {
282             personService = SpringContext.getBean(PersonService.class);
283         }
284         return personService;
285     }
286 
287 }