001: /*
002: * Copyright 2007 The Kuali Foundation.
003: *
004: * Licensed under the Educational Community License, Version 1.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/ecl1.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.module.purap.service.impl;
017:
018: import java.security.InvalidParameterException;
019: import java.sql.Timestamp;
020: import java.util.ArrayList;
021: import java.util.Iterator;
022: import java.util.List;
023:
024: import org.apache.commons.lang.StringUtils;
025: import org.kuali.core.bo.user.AuthenticationUserId;
026: import org.kuali.core.bo.user.UniversalUser;
027: import org.kuali.core.document.Document;
028: import org.kuali.core.exceptions.UserNotFoundException;
029: import org.kuali.core.service.UniversalUserService;
030: import org.kuali.core.util.GlobalVariables;
031: import org.kuali.core.util.ObjectUtils;
032: import org.kuali.core.workflow.service.KualiWorkflowDocument;
033: import org.kuali.core.workflow.service.KualiWorkflowInfo;
034: import org.kuali.core.workflow.service.WorkflowDocumentService;
035: import org.kuali.kfs.context.SpringContext;
036: import org.kuali.module.purap.PurapWorkflowConstants.NodeDetails;
037: import org.kuali.module.purap.document.PurchasingAccountsPayableDocument;
038: import org.kuali.module.purap.service.PurApWorkflowIntegrationService;
039: import org.springframework.transaction.annotation.Transactional;
040:
041: import edu.iu.uis.eden.EdenConstants;
042: import edu.iu.uis.eden.actiontaken.ActionTakenValue;
043: import edu.iu.uis.eden.clientapp.vo.ActionRequestVO;
044: import edu.iu.uis.eden.clientapp.vo.NetworkIdVO;
045: import edu.iu.uis.eden.clientapp.vo.ReportCriteriaVO;
046: import edu.iu.uis.eden.clientapp.vo.UserIdVO;
047: import edu.iu.uis.eden.exception.EdenUserNotFoundException;
048: import edu.iu.uis.eden.exception.WorkflowException;
049: import edu.iu.uis.eden.routeheader.DocumentRouteHeaderValue;
050: import edu.iu.uis.eden.user.WorkflowUser;
051:
052: /**
053: * This class holds methods for Purchasing and Accounts Payable documents to integrate with workflow services and operations.
054: */
055: @Transactional
056: public class PurApWorkflowIntegrationServiceImpl implements
057: PurApWorkflowIntegrationService {
058: private static org.apache.log4j.Logger LOG = org.apache.log4j.Logger
059: .getLogger(PurApWorkflowIntegrationServiceImpl.class);
060:
061: private KualiWorkflowInfo kualiWorkflowInfo;
062: private WorkflowDocumentService workflowDocumentService;
063:
064: public void setKualiWorkflowInfo(KualiWorkflowInfo kualiWorkflowInfo) {
065: this .kualiWorkflowInfo = kualiWorkflowInfo;
066: }
067:
068: public void setWorkflowDocumentService(
069: WorkflowDocumentService workflowDocumentService) {
070: this .workflowDocumentService = workflowDocumentService;
071: }
072:
073: private UserIdVO getUserIdVO(UniversalUser user) {
074: return new NetworkIdVO(user.getPersonUserIdentifier());
075: }
076:
077: /**
078: * @see org.kuali.module.purap.service.PurApWorkflowIntegrationService#isActionRequestedOfUserAtNodeName(java.lang.String,
079: * java.lang.String, org.kuali.core.bo.user.UniversalUser)
080: */
081: public boolean isActionRequestedOfUserAtNodeName(
082: String documentNumber, String nodeName,
083: UniversalUser userToCheck) {
084: try {
085: List<ActionRequestVO> actionRequests = getActiveActionRequestsForCriteria(
086: Long.valueOf(documentNumber), nodeName, userToCheck);
087: return !actionRequests.isEmpty();
088: } catch (WorkflowException e) {
089: String errorMessage = "Error trying to get test action requests of document id '"
090: + documentNumber + "'";
091: LOG.error("isActionRequestedOfUserAtNodeName() "
092: + errorMessage, e);
093: throw new RuntimeException(errorMessage, e);
094: }
095: }
096:
097: /**
098: * Performs a super user approval of all action requests.
099: *
100: * @param superUser
101: * @param documentNumber
102: * @param nodeName
103: * @param user
104: * @param annotation
105: * @throws WorkflowException
106: */
107: private void super UserApproveAllActionRequests(
108: UniversalUser super User, Long documentNumber,
109: String nodeName, UniversalUser user, String annotation)
110: throws WorkflowException {
111: KualiWorkflowDocument workflowDoc = workflowDocumentService
112: .createWorkflowDocument(documentNumber, super User);
113: List<ActionRequestVO> actionRequests = getActiveActionRequestsForCriteria(
114: documentNumber, nodeName, user);
115: for (ActionRequestVO actionRequestVO : actionRequests) {
116: LOG.debug("Active Action Request list size to process is "
117: + actionRequests.size());
118: LOG
119: .debug("Attempting to super user approve action request with id "
120: + actionRequestVO.getActionRequestId());
121: workflowDoc.super UserActionRequestApprove(actionRequestVO
122: .getActionRequestId(), annotation);
123: super UserApproveAllActionRequests(super User,
124: documentNumber, nodeName, user, annotation);
125: break;
126: }
127: }
128:
129: /**
130: * @see org.kuali.module.purap.service.PurApWorkflowIntegrationService#takeAllActionsForGivenCriteria(org.kuali.core.document.Document,
131: * java.lang.String, java.lang.String, org.kuali.core.bo.user.UniversalUser, java.lang.String)
132: */
133: public boolean takeAllActionsForGivenCriteria(Document document,
134: String potentialAnnotation, String nodeName,
135: UniversalUser userToCheck, String super UserNetworkId) {
136: try {
137: Long documentNumber = document.getDocumentHeader()
138: .getWorkflowDocument().getRouteHeaderId();
139: String networkIdString = (ObjectUtils
140: .isNotNull(userToCheck)) ? userToCheck
141: .getPersonUserIdentifier() : "none";
142: List<ActionRequestVO> activeActionRequests = getActiveActionRequestsForCriteria(
143: documentNumber, nodeName, userToCheck);
144:
145: // if no action requests are found... no actions required
146: if (activeActionRequests.isEmpty()) {
147: LOG
148: .debug("No action requests found on document id "
149: + documentNumber
150: + " for given criteria: personUserIdentifier - "
151: + networkIdString
152: + "; nodeName - "
153: + nodeName);
154: return false;
155: }
156:
157: // if a super user network id was given... take all actions as super user
158: if (StringUtils.isNotBlank(super UserNetworkId)) {
159: // approve each action request as the super user
160: UniversalUser super User = SpringContext.getBean(
161: UniversalUserService.class).getUniversalUser(
162: new AuthenticationUserId(super UserNetworkId));
163: LOG
164: .debug("Attempting to super user approve all action requests found on document id "
165: + documentNumber
166: + " for given criteria: personUserIdentifier - "
167: + networkIdString
168: + "; nodeName - "
169: + nodeName);
170: super UserApproveAllActionRequests(super User,
171: documentNumber, nodeName, userToCheck,
172: potentialAnnotation);
173: return true;
174: } else {
175: // if a user was given... take the action as that user
176: if (ObjectUtils.isNotNull(userToCheck)) {
177: KualiWorkflowDocument workflowDocument = workflowDocumentService
178: .createWorkflowDocument(documentNumber,
179: userToCheck);
180: boolean containsFyiRequest = false;
181: boolean containsAckRequest = false;
182: boolean containsApproveRequest = false;
183: boolean containsCompleteRequest = false;
184: if (StringUtils.isBlank(nodeName)) {
185: // requests are for a specific user but not at a specific level... take regular actions
186: containsCompleteRequest = workflowDocument
187: .isCompletionRequested();
188: containsApproveRequest = workflowDocument
189: .isApprovalRequested();
190: containsAckRequest = workflowDocument
191: .isAcknowledgeRequested();
192: containsFyiRequest = workflowDocument
193: .isFYIRequested();
194: } else {
195: for (ActionRequestVO actionRequestVO : activeActionRequests) {
196: containsFyiRequest |= (EdenConstants.ACTION_REQUEST_FYI_REQ
197: .equals(actionRequestVO
198: .getActionRequested()));
199: containsAckRequest |= (EdenConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ
200: .equals(actionRequestVO
201: .getActionRequested()));
202: containsApproveRequest |= (EdenConstants.ACTION_REQUEST_APPROVE_REQ
203: .equals(actionRequestVO
204: .getActionRequested()));
205: containsCompleteRequest |= (EdenConstants.ACTION_REQUEST_COMPLETE_REQ
206: .equals(actionRequestVO
207: .getActionRequested()));
208: }
209: }
210: if (containsCompleteRequest
211: || containsApproveRequest) {
212: workflowDocumentService.approve(
213: workflowDocument, potentialAnnotation,
214: new ArrayList());
215: return true;
216: } else if (containsAckRequest) {
217: workflowDocumentService.acknowledge(
218: workflowDocument, potentialAnnotation,
219: new ArrayList());
220: return true;
221: } else if (containsFyiRequest) {
222: workflowDocumentService.clearFyi(
223: workflowDocument, new ArrayList());
224: return true;
225: }
226: } else {
227: // no user to check and no super user given... cannot take actions on document
228: String errorMessage = "No super user network id and no user to check given. Need at least one or both";
229: LOG.error(errorMessage);
230: throw new RuntimeException(errorMessage);
231: }
232: }
233: return false;
234: } catch (WorkflowException e) {
235: String errorMessage = "Error trying to get action requests of document id '"
236: + document.getDocumentNumber() + "'";
237: LOG.error("takeAllActionsForGivenCriteria() "
238: + errorMessage, e);
239: throw new RuntimeException(errorMessage, e);
240: } catch (UserNotFoundException e) {
241: String errorMessage = "Error trying to get user for network id '"
242: + super UserNetworkId + "'";
243: LOG.error("takeAllActionsForGivenCriteria() "
244: + errorMessage, e);
245: throw new RuntimeException(errorMessage, e);
246: }
247: }
248:
249: /**
250: * Retrieves the active action requests for the given criteria
251: *
252: * @param documentNumber
253: * @param nodeName
254: * @param user
255: * @return List of action requests
256: * @throws WorkflowException
257: */
258: private List<ActionRequestVO> getActiveActionRequestsForCriteria(
259: Long documentNumber, String nodeName, UniversalUser user)
260: throws WorkflowException {
261: if (ObjectUtils.isNull(documentNumber)) {
262: // throw exception
263: }
264: List<ActionRequestVO> activeRequests = new ArrayList<ActionRequestVO>();
265: UserIdVO userIdVO = (ObjectUtils.isNotNull(user)) ? new NetworkIdVO(
266: user.getPersonUserIdentifier())
267: : null;
268: ActionRequestVO[] actionRequests = kualiWorkflowInfo
269: .getActionRequests(documentNumber, nodeName, userIdVO);
270: for (ActionRequestVO actionRequest : actionRequests) {
271: // identify which requests for the given node name can be satisfied by an action by this user
272: if (actionRequest.isActivated()) {
273: activeRequests.add(actionRequest);
274: }
275: }
276: return activeRequests;
277: }
278:
279: /**
280: * @see org.kuali.module.purap.service.PurApWorkflowIntegrationService#willDocumentStopAtGivenFutureRouteNode(org.kuali.module.purap.document.PurchasingAccountsPayableDocument,
281: * org.kuali.module.purap.PurapWorkflowConstants.NodeDetails)
282: */
283: public boolean willDocumentStopAtGivenFutureRouteNode(
284: PurchasingAccountsPayableDocument document,
285: NodeDetails givenNodeDetail) {
286: if (givenNodeDetail == null) {
287: throw new InvalidParameterException(
288: "Given Node Detail object was null");
289: }
290: try {
291: String activeNode = null;
292: String[] nodeNames = document.getDocumentHeader()
293: .getWorkflowDocument().getNodeNames();
294: if (nodeNames.length == 1) {
295: activeNode = nodeNames[0];
296: }
297: if (isGivenNodeAfterCurrentNode(givenNodeDetail
298: .getNodeDetailByName(activeNode), givenNodeDetail)) {
299: if (document.getDocumentHeader().getWorkflowDocument()
300: .stateIsInitiated()) {
301: // document is only initiated so we need to pass xml for workflow to simulate route properly
302: ReportCriteriaVO reportCriteriaVO = new ReportCriteriaVO(
303: document.getDocumentHeader()
304: .getWorkflowDocument()
305: .getDocumentType());
306: reportCriteriaVO.setXmlContent(document
307: .getXmlForRouteReport());
308: reportCriteriaVO.setRoutingUser(new NetworkIdVO(
309: GlobalVariables.getUserSession()
310: .getUniversalUser()
311: .getPersonUserIdentifier()));
312: reportCriteriaVO.setTargetNodeName(givenNodeDetail
313: .getName());
314: boolean value = kualiWorkflowInfo
315: .documentWillHaveAtLeastOneActionRequest(
316: reportCriteriaVO,
317: new String[] {
318: EdenConstants.ACTION_REQUEST_APPROVE_REQ,
319: EdenConstants.ACTION_REQUEST_COMPLETE_REQ });
320: return value;
321: } else {
322: /*
323: * Document has had at least one workflow action taken so we need to pass the doc id so the simulation will use
324: * the existing actions taken and action requests in determining if rules will fire or not. We also need to call
325: * a save routing data so that the xml Workflow uses represents what is currently on the document
326: */
327: ReportCriteriaVO reportCriteriaVO = new ReportCriteriaVO(
328: Long.valueOf(document.getDocumentNumber()));
329: reportCriteriaVO.setXmlContent(document
330: .getXmlForRouteReport());
331: reportCriteriaVO.setTargetNodeName(givenNodeDetail
332: .getName());
333: boolean value = kualiWorkflowInfo
334: .documentWillHaveAtLeastOneActionRequest(
335: reportCriteriaVO,
336: new String[] {
337: EdenConstants.ACTION_REQUEST_APPROVE_REQ,
338: EdenConstants.ACTION_REQUEST_COMPLETE_REQ });
339: return value;
340: }
341: }
342: return false;
343: } catch (WorkflowException e) {
344: String errorMessage = "Error trying to test document id '"
345: + document.getDocumentNumber()
346: + "' for action requests at node name '"
347: + givenNodeDetail.getName() + "'";
348: LOG.error("isDocumentStoppingAtRouteLevel() "
349: + errorMessage, e);
350: throw new RuntimeException(errorMessage, e);
351: }
352: }
353:
354: /**
355: * Evaluates if given node is after the current node
356: *
357: * @param currentNodeDetail
358: * @param givenNodeDetail
359: * @return boolean to indicate if given node is after the current node
360: */
361: private boolean isGivenNodeAfterCurrentNode(
362: NodeDetails currentNodeDetail, NodeDetails givenNodeDetail) {
363: if (ObjectUtils.isNull(givenNodeDetail)) {
364: // given node does not exist
365: return false;
366: }
367: if (ObjectUtils.isNull(currentNodeDetail)) {
368: // current node does not exist... assume we are pre-route
369: return true;
370: }
371: return givenNodeDetail.getOrdinal() > currentNodeDetail
372: .getOrdinal();
373: }
374:
375: /**
376: * @see org.kuali.module.purap.service.PurApWorkflowIntegrationService#getLastUserId(edu.iu.uis.eden.routeheader.DocumentRouteHeaderValue)
377: */
378: public String getLastUserId(DocumentRouteHeaderValue routeHeader)
379: throws EdenUserNotFoundException {
380: WorkflowUser user = null;
381: Timestamp previousDate = null;
382: for (Iterator iter = routeHeader.getActionsTaken().iterator(); iter
383: .hasNext();) {
384: ActionTakenValue actionTaken = (ActionTakenValue) iter
385: .next();
386:
387: if (previousDate != null) {
388: if (actionTaken.getActionDate().after(previousDate)) {
389: user = actionTaken.getWorkflowUser();
390: previousDate = actionTaken.getActionDate();
391: }
392: } else {
393: previousDate = actionTaken.getActionDate();
394: user = actionTaken.getWorkflowUser();
395: }
396: }
397: if (user != null && user.getAuthenticationUserId() != null) {
398: return user.getAuthenticationUserId().getAuthenticationId();
399: } else {
400: return null;
401: }
402: }
403:
404: }
|