001/** 002 * Copyright 2005-2016 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 */ 016package org.kuali.rice.kew.actions; 017 018import org.apache.commons.lang.StringUtils; 019import org.apache.log4j.MDC; 020import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator; 021import org.kuali.rice.kew.actionrequest.ActionRequestFactory; 022import org.kuali.rice.kew.actionrequest.ActionRequestValue; 023import org.kuali.rice.kew.api.doctype.DocumentTypePolicy; 024import org.kuali.rice.kew.actionrequest.Recipient; 025import org.kuali.rice.kew.actiontaken.ActionTakenValue; 026import org.kuali.rice.kew.api.exception.WorkflowException; 027import org.kuali.rice.kew.engine.node.RouteNodeInstance; 028import org.kuali.rice.kew.api.exception.InvalidActionTakenException; 029import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue; 030import org.kuali.rice.kew.service.KEWServiceLocator; 031import org.kuali.rice.kew.api.KewApiConstants; 032import org.kuali.rice.kew.util.Utilities; 033import org.kuali.rice.kim.api.group.Group; 034import org.kuali.rice.kim.api.identity.principal.PrincipalContract; 035import org.kuali.rice.kim.api.services.KimApiServiceLocator; 036import org.kuali.rice.krad.util.KRADConstants; 037 038import java.util.Collection; 039import java.util.HashSet; 040import java.util.Iterator; 041import java.util.List; 042import java.util.Set; 043 044 045/** 046 * Disapproves a document. This deactivates all requests on the document and sends 047 * acknowlegde requests to anybody who had already completed or approved the document. 048 * 049 * @author Kuali Rice Team (rice.collab@kuali.org) 050 */ 051public class DisapproveAction extends ActionTakenEvent { 052 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DisapproveAction.class); 053 054 /** 055 * @param rh RouteHeader for the document upon which the action is taken. 056 * @param principal User taking the action. 057 */ 058 public DisapproveAction(DocumentRouteHeaderValue rh, PrincipalContract principal) { 059 super(KewApiConstants.ACTION_TAKEN_DENIED_CD, rh, principal); 060 } 061 062 /** 063 * @param rh RouteHeader for the document upon which the action is taken. 064 * @param principal User taking the action. 065 * @param annotation User comment on the action taken 066 */ 067 public DisapproveAction(DocumentRouteHeaderValue rh, PrincipalContract principal, String annotation) { 068 super(KewApiConstants.ACTION_TAKEN_DENIED_CD, rh, principal, annotation); 069 } 070 071 /* (non-Javadoc) 072 * @see org.kuali.rice.kew.actions.ActionTakenEvent#isActionCompatibleRequest(java.util.List) 073 */ 074 @Override 075 public String validateActionRules() { 076 return validateActionRules(getActionRequestService().findAllPendingRequests(routeHeader.getDocumentId())); 077 } 078 079 public String validateActionRules(List<ActionRequestValue> actionRequests) { 080 if (!getRouteHeader().isValidActionToTake(getActionPerformedCode())) { 081 return "Document is not in a state to be disapproved"; 082 } 083 List<ActionRequestValue> filteredActionRequests = filterActionRequestsByCode(actionRequests, KewApiConstants.ACTION_REQUEST_COMPLETE_REQ); 084 if (!isActionCompatibleRequest(filteredActionRequests)) { 085 return "No request for the user is compatible " + "with the DISAPPROVE or DENY action"; 086 } 087 return ""; 088 } 089 090 /* (non-Javadoc) 091 * @see org.kuali.rice.kew.actions.ActionTakenEvent#isActionCompatibleRequest(java.util.List) 092 */ 093 @Override 094 public boolean isActionCompatibleRequest(List requests) { 095 // can always cancel saved or initiated document 096 if (routeHeader.isStateInitiated() || routeHeader.isStateSaved()) { 097 return true; 098 } 099 100 boolean actionCompatible = false; 101 Iterator ars = requests.iterator(); 102 ActionRequestValue actionRequest = null; 103 104 while (ars.hasNext()) { 105 actionRequest = (ActionRequestValue) ars.next(); 106 String request = actionRequest.getActionRequested(); 107 108 // APPROVE request matches all but FYI and ACK 109 if ( (KewApiConstants.ACTION_REQUEST_APPROVE_REQ.equals(request)) || 110 (KewApiConstants.ACTION_REQUEST_COMPLETE_REQ.equals(request)) ) { 111 actionCompatible = true; 112 break; 113 } 114 } 115 116 return actionCompatible; 117 } 118 119 /** 120 * Records the disapprove action. - Checks to make sure the document status allows the action. - Checks that the user has not taken a previous action. - Deactivates the pending requests for this user - Records the action 121 * 122 * @throws org.kuali.rice.kew.api.exception.InvalidActionTakenException 123 */ 124 public void recordAction() throws InvalidActionTakenException { 125 MDC.put("docId", getRouteHeader().getDocumentId()); 126 updateSearchableAttributesIfPossible(); 127 128 LOG.debug("Disapproving document : " + annotation); 129 130 List actionRequests = getActionRequestService().findAllValidRequests(getPrincipal().getPrincipalId(), getDocumentId(), KewApiConstants.ACTION_REQUEST_COMPLETE_REQ); 131 LOG.debug("Checking to see if the action is legal"); 132 String errorMessage = validateActionRules(actionRequests); 133 if (!org.apache.commons.lang.StringUtils.isEmpty(errorMessage)) { 134 throw new InvalidActionTakenException(errorMessage); 135 } 136 137 LOG.debug("Record the disapproval action"); 138 Recipient delegator = findDelegatorForActionRequests(actionRequests); 139 ActionTakenValue actionTaken = saveActionTaken(delegator); 140 141 LOG.debug("Deactivate all pending action requests"); 142 actionRequests = getActionRequestService().findPendingByDoc(getDocumentId()); 143 getActionRequestService().deactivateRequests(actionTaken, actionRequests); 144 notifyActionTaken(actionTaken); 145 146 if(!isPolicySet(getRouteHeader().getDocumentType(), DocumentTypePolicy.SUPPRESS_ACKNOWLEDGEMENTS_ON_DISAPPROVE)){ 147 LOG.debug("Sending Acknowledgements to all previous approvers/completers"); 148 // Generate the notification requests in the first node we find that the current user has an approve request 149 RouteNodeInstance notificationNodeInstance = null; 150 // if (actionRequests.size() > 0) { //I don't see why this matters let me know if it does rk 151 notificationNodeInstance = ((ActionRequestValue)actionRequests.get(0)).getNodeInstance(); 152 // } 153 generateAcknowledgementsToPreviousActionTakers(notificationNodeInstance); 154 } 155 156 LOG.debug("Disapproving document"); 157 try { 158 String oldStatus = getRouteHeader().getDocRouteStatus(); 159 routeHeader.markDocumentDisapproved(); 160 String newStatus = getRouteHeader().getDocRouteStatus(); 161 DocumentRouteHeaderValue routeHeaderValue = KEWServiceLocator.getRouteHeaderService(). 162 saveRouteHeader(routeHeader); 163 setRouteHeader(routeHeaderValue); 164 notifyStatusChange(newStatus, oldStatus); 165 } catch (WorkflowException ex) { 166 LOG.warn(ex, ex); 167 throw new InvalidActionTakenException(ex.getMessage()); 168 } 169 } 170}