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    }