001 /*
002 * Copyright 2011 The Kuali Foundation.
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.kfs.module.purap.document.service.impl;
017
018 import java.security.InvalidParameterException;
019 import java.util.ArrayList;
020 import java.util.List;
021
022 import org.apache.commons.lang.StringUtils;
023 import org.kuali.kfs.module.purap.PurapWorkflowConstants.NodeDetails;
024 import org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument;
025 import org.kuali.kfs.module.purap.document.service.PurApWorkflowIntegrationService;
026 import org.kuali.kfs.sys.context.SpringContext;
027 import org.kuali.rice.kew.dto.ActionRequestDTO;
028 import org.kuali.rice.kew.dto.ReportCriteriaDTO;
029 import org.kuali.rice.kew.exception.WorkflowException;
030 import org.kuali.rice.kew.service.WorkflowInfo;
031 import org.kuali.rice.kew.util.KEWConstants;
032 import org.kuali.rice.kim.bo.Person;
033 import org.kuali.rice.kim.service.PersonService;
034 import org.kuali.rice.kns.document.Document;
035 import org.kuali.rice.kns.util.GlobalVariables;
036 import org.kuali.rice.kns.util.ObjectUtils;
037 import org.kuali.rice.kns.workflow.service.KualiWorkflowDocument;
038 import org.kuali.rice.kns.workflow.service.WorkflowDocumentService;
039 import org.springframework.transaction.annotation.Transactional;
040
041 /**
042 * This class holds methods for Purchasing and Accounts Payable documents to integrate with workflow services and operations.
043 */
044 @Transactional
045 public class PurApWorkflowIntegrationServiceImpl implements PurApWorkflowIntegrationService {
046 private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(PurApWorkflowIntegrationServiceImpl.class);
047
048 private WorkflowInfo workflowInfo;
049 private WorkflowDocumentService workflowDocumentService;
050 private PersonService<Person> personService;
051
052
053 public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
054 this.workflowDocumentService = workflowDocumentService;
055 }
056
057 /**
058 * Performs a super user approval of all action requests.
059 *
060 * @param superUser
061 * @param documentNumber
062 * @param nodeName
063 * @param user
064 * @param annotation
065 * @throws WorkflowException
066 */
067 protected void superUserApproveAllActionRequests(Person superUser, Long documentNumber, String nodeName, Person user, String annotation) throws WorkflowException {
068 KualiWorkflowDocument workflowDoc = workflowDocumentService.createWorkflowDocument(documentNumber, superUser);
069 List<ActionRequestDTO> actionRequests = getActiveActionRequestsForCriteria(documentNumber, nodeName, user);
070 for (ActionRequestDTO actionRequestDTO : actionRequests) {
071 LOG.debug("Active Action Request list size to process is " + actionRequests.size());
072 LOG.debug("Attempting to super user approve action request with id " + actionRequestDTO.getActionRequestId());
073 workflowDoc.superUserActionRequestApprove(actionRequestDTO.getActionRequestId(), annotation);
074 superUserApproveAllActionRequests(superUser, documentNumber, nodeName, user, annotation);
075 break;
076 }
077 }
078
079 /**
080 * @see org.kuali.kfs.module.purap.document.service.PurApWorkflowIntegrationService#takeAllActionsForGivenCriteria(org.kuali.rice.kns.document.Document,
081 * java.lang.String, java.lang.String, org.kuali.rice.kim.bo.Person, java.lang.String)
082 */
083 public boolean takeAllActionsForGivenCriteria(Document document, String potentialAnnotation, String nodeName, Person userToCheck, String superUserNetworkId) {
084 try {
085 Long documentNumber = document.getDocumentHeader().getWorkflowDocument().getRouteHeaderId();
086 String networkIdString = (ObjectUtils.isNotNull(userToCheck)) ? userToCheck.getPrincipalName() : "none";
087 List<ActionRequestDTO> activeActionRequests = getActiveActionRequestsForCriteria(documentNumber, nodeName, userToCheck);
088
089 // if no action requests are found... no actions required
090 if (activeActionRequests.isEmpty()) {
091 LOG.debug("No action requests found on document id " + documentNumber + " for given criteria: principalName - " + networkIdString + "; nodeName - " + nodeName);
092 return false;
093 }
094
095 // if a super user network id was given... take all actions as super user
096 if (StringUtils.isNotBlank(superUserNetworkId)) {
097 // approve each action request as the super user
098 Person superUser = getPersonService().getPersonByPrincipalName(superUserNetworkId);
099 LOG.debug("Attempting to super user approve all action requests found on document id " + documentNumber + " for given criteria: principalName - " + networkIdString + "; nodeName - " + nodeName);
100 superUserApproveAllActionRequests(superUser, documentNumber, nodeName, userToCheck, potentialAnnotation);
101 return true;
102 }
103 else {
104 // if a user was given... take the action as that user
105 if (ObjectUtils.isNotNull(userToCheck)) {
106 KualiWorkflowDocument workflowDocument = workflowDocumentService.createWorkflowDocument(documentNumber, userToCheck);
107 boolean containsFyiRequest = false;
108 boolean containsAckRequest = false;
109 boolean containsApproveRequest = false;
110 boolean containsCompleteRequest = false;
111 if (StringUtils.isBlank(nodeName)) {
112 // requests are for a specific user but not at a specific level... take regular actions
113 containsCompleteRequest = workflowDocument.isCompletionRequested();
114 containsApproveRequest = workflowDocument.isApprovalRequested();
115 containsAckRequest = workflowDocument.isAcknowledgeRequested();
116 containsFyiRequest = workflowDocument.isFYIRequested();
117 }
118 else {
119 for (ActionRequestDTO actionRequestDTO : activeActionRequests) {
120 containsFyiRequest |= (KEWConstants.ACTION_REQUEST_FYI_REQ.equals(actionRequestDTO.getActionRequested()));
121 containsAckRequest |= (KEWConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ.equals(actionRequestDTO.getActionRequested()));
122 containsApproveRequest |= (KEWConstants.ACTION_REQUEST_APPROVE_REQ.equals(actionRequestDTO.getActionRequested()));
123 containsCompleteRequest |= (KEWConstants.ACTION_REQUEST_COMPLETE_REQ.equals(actionRequestDTO.getActionRequested()));
124 }
125 }
126 if (containsCompleteRequest || containsApproveRequest) {
127 workflowDocumentService.approve(workflowDocument, potentialAnnotation, new ArrayList());
128 return true;
129 }
130 else if (containsAckRequest) {
131 workflowDocumentService.acknowledge(workflowDocument, potentialAnnotation, new ArrayList());
132 return true;
133 }
134 else if (containsFyiRequest) {
135 workflowDocumentService.clearFyi(workflowDocument, new ArrayList());
136 return true;
137 }
138 }
139 else {
140 // no user to check and no super user given... cannot take actions on document
141 String errorMessage = "No super user network id and no user to check given. Need at least one or both";
142 LOG.error(errorMessage);
143 throw new RuntimeException(errorMessage);
144 }
145 }
146 return false;
147 }
148 catch (WorkflowException e) {
149 String errorMessage = "Error trying to get action requests of document id '" + document.getDocumentNumber() + "'";
150 LOG.error("takeAllActionsForGivenCriteria() " + errorMessage, e);
151 throw new RuntimeException(errorMessage, e);
152 }
153 catch (Exception e) {
154 String errorMessage = "Error trying to get user for network id '" + superUserNetworkId + "'";
155 LOG.error("takeAllActionsForGivenCriteria() " + errorMessage, e);
156 throw new RuntimeException(errorMessage, e);
157 }
158 }
159
160 /**
161 * Retrieves the active action requests for the given criteria
162 *
163 * @param documentNumber
164 * @param nodeName
165 * @param user
166 * @return List of action requests
167 * @throws WorkflowException
168 */
169 protected List<ActionRequestDTO> getActiveActionRequestsForCriteria(Long documentNumber, String nodeName, Person user) throws WorkflowException {
170 if (ObjectUtils.isNull(documentNumber)) {
171 // throw exception
172 }
173 List<ActionRequestDTO> activeRequests = new ArrayList<ActionRequestDTO>();
174 ActionRequestDTO[] actionRequests = getWorkflowInfo().getActionRequests(documentNumber, nodeName, user.getPrincipalId());
175 for (ActionRequestDTO actionRequest : actionRequests) {
176 // identify which requests for the given node name can be satisfied by an action by this user
177 if (actionRequest.isActivated()) {
178 activeRequests.add(actionRequest);
179 }
180 }
181 return activeRequests;
182 }
183
184 /**
185 * DON'T CALL THIS IF THE DOC HAS NOT BEEN SAVED
186 *
187 * @see org.kuali.kfs.module.purap.document.service.PurApWorkflowIntegrationService#willDocumentStopAtGivenFutureRouteNode(org.kuali.kfs.module.purap.document.PurchasingAccountsPayableDocument,
188 * org.kuali.kfs.module.purap.PurapWorkflowConstants.NodeDetails)
189 */
190 public boolean willDocumentStopAtGivenFutureRouteNode(PurchasingAccountsPayableDocument document, NodeDetails givenNodeDetail) {
191 if (givenNodeDetail == null) {
192 throw new InvalidParameterException("Given Node Detail object was null");
193 }
194 try {
195 String activeNode = null;
196 String[] nodeNames = document.getDocumentHeader().getWorkflowDocument().getNodeNames();
197 if (nodeNames.length == 1) {
198 activeNode = nodeNames[0];
199 }
200 if (isGivenNodeAfterCurrentNode(givenNodeDetail.getNodeDetailByName(activeNode), givenNodeDetail)) {
201 if (document.getDocumentHeader().getWorkflowDocument().stateIsInitiated()) {
202 // document is only initiated so we need to pass xml for workflow to simulate route properly
203 ReportCriteriaDTO reportCriteriaDTO = new ReportCriteriaDTO(document.getDocumentHeader().getWorkflowDocument().getDocumentType());
204 reportCriteriaDTO.setXmlContent(document.getXmlForRouteReport());
205 reportCriteriaDTO.setRoutingPrincipalId(GlobalVariables.getUserSession().getPerson().getPrincipalId());
206 reportCriteriaDTO.setTargetNodeName(givenNodeDetail.getName());
207 boolean value = getWorkflowInfo().documentWillHaveAtLeastOneActionRequest(reportCriteriaDTO, new String[] { KEWConstants.ACTION_REQUEST_APPROVE_REQ, KEWConstants.ACTION_REQUEST_COMPLETE_REQ }, false);
208 return value;
209 }
210 else {
211 /*
212 * Document has had at least one workflow action taken so we need to pass the doc id so the simulation will use
213 * the existing actions taken and action requests in determining if rules will fire or not. We also need to call
214 * a save routing data so that the xml Workflow uses represents what is currently on the document
215 */
216 ReportCriteriaDTO reportCriteriaDTO = new ReportCriteriaDTO(Long.valueOf(document.getDocumentNumber()));
217 reportCriteriaDTO.setXmlContent(document.getXmlForRouteReport());
218 reportCriteriaDTO.setTargetNodeName(givenNodeDetail.getName());
219 boolean value = getWorkflowInfo().documentWillHaveAtLeastOneActionRequest(reportCriteriaDTO, new String[] { KEWConstants.ACTION_REQUEST_APPROVE_REQ, KEWConstants.ACTION_REQUEST_COMPLETE_REQ }, false);
220 return value;
221 }
222 }
223 return false;
224 }
225 catch (WorkflowException e) {
226 String errorMessage = "Error trying to test document id '" + document.getDocumentNumber() + "' for action requests at node name '" + givenNodeDetail.getName() + "'";
227 LOG.error("isDocumentStoppingAtRouteLevel() " + errorMessage, e);
228 throw new RuntimeException(errorMessage, e);
229 }
230 }
231
232 /**
233 * Evaluates if given node is after the current node
234 *
235 * @param currentNodeDetail
236 * @param givenNodeDetail
237 * @return boolean to indicate if given node is after the current node
238 */
239 protected boolean isGivenNodeAfterCurrentNode(NodeDetails currentNodeDetail, NodeDetails givenNodeDetail) {
240 if (ObjectUtils.isNull(givenNodeDetail)) {
241 // given node does not exist
242 return false;
243 }
244 if (ObjectUtils.isNull(currentNodeDetail)) {
245 // current node does not exist... assume we are pre-route
246 return true;
247 }
248 return givenNodeDetail.getOrdinal() > currentNodeDetail.getOrdinal();
249 }
250
251 protected WorkflowInfo getWorkflowInfo() {
252 if (workflowInfo == null) {
253 workflowInfo = new WorkflowInfo();
254 }
255 return workflowInfo;
256 }
257
258 /**
259 * @return Returns the personService.
260 */
261 protected PersonService<Person> getPersonService() {
262 if(personService==null)
263 personService = SpringContext.getBean(PersonService.class);
264 return personService;
265 }
266
267 }