001 /* 002 * Copyright 2006-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 017 package org.kuali.rice.kew.actions; 018 019 import org.apache.commons.lang.StringUtils; 020 import org.apache.log4j.MDC; 021 import org.kuali.rice.core.framework.services.CoreFrameworkServiceLocator; 022 import org.kuali.rice.kew.actionrequest.ActionRequestFactory; 023 import org.kuali.rice.kew.actionrequest.ActionRequestValue; 024 import org.kuali.rice.kew.actionrequest.Recipient; 025 import org.kuali.rice.kew.actiontaken.ActionTakenValue; 026 import org.kuali.rice.kew.engine.node.RouteNodeInstance; 027 import org.kuali.rice.kew.exception.InvalidActionTakenException; 028 import org.kuali.rice.kew.exception.WorkflowException; 029 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue; 030 import org.kuali.rice.kew.service.KEWServiceLocator; 031 import org.kuali.rice.kew.util.KEWConstants; 032 import org.kuali.rice.kew.util.Utilities; 033 import org.kuali.rice.kim.api.entity.principal.PrincipalContract; 034 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 035 import org.kuali.rice.kim.api.group.Group; 036 037 import org.kuali.rice.kns.util.KNSConstants; 038 039 import java.util.Collection; 040 import java.util.HashSet; 041 import java.util.Iterator; 042 import java.util.List; 043 import java.util.Set; 044 045 046 /** 047 * Disapproves a document. This deactivates all requests on the document and sends 048 * acknowlegde requests to anybody who had already completed or approved the document. 049 * 050 * @author Kuali Rice Team (rice.collab@kuali.org) 051 */ 052 public class DisapproveAction extends ActionTakenEvent { 053 private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(DisapproveAction.class); 054 055 /** 056 * @param rh RouteHeader for the document upon which the action is taken. 057 * @param principal User taking the action. 058 */ 059 public DisapproveAction(DocumentRouteHeaderValue rh, PrincipalContract principal) { 060 super(KEWConstants.ACTION_TAKEN_DENIED_CD, rh, principal); 061 } 062 063 /** 064 * @param rh RouteHeader for the document upon which the action is taken. 065 * @param principal User taking the action. 066 * @param annotation User comment on the action taken 067 */ 068 public DisapproveAction(DocumentRouteHeaderValue rh, PrincipalContract principal, String annotation) { 069 super(KEWConstants.ACTION_TAKEN_DENIED_CD, rh, principal, annotation); 070 } 071 072 /* (non-Javadoc) 073 * @see org.kuali.rice.kew.actions.ActionTakenEvent#isActionCompatibleRequest(java.util.List) 074 */ 075 @Override 076 public String validateActionRules() { 077 return validateActionRules(getActionRequestService().findAllPendingRequests(routeHeader.getDocumentId())); 078 } 079 080 public String validateActionRules(List<ActionRequestValue> actionRequests) { 081 if (!getRouteHeader().isValidActionToTake(getActionPerformedCode())) { 082 return "Document is not in a state to be disapproved"; 083 } 084 List<ActionRequestValue> filteredActionRequests = filterActionRequestsByCode(actionRequests, KEWConstants.ACTION_REQUEST_COMPLETE_REQ); 085 if (!isActionCompatibleRequest(filteredActionRequests)) { 086 return "No request for the user is compatible " + "with the DISAPPROVE or DENY action"; 087 } 088 return ""; 089 } 090 091 /* (non-Javadoc) 092 * @see org.kuali.rice.kew.actions.ActionTakenEvent#isActionCompatibleRequest(java.util.List) 093 */ 094 @Override 095 public boolean isActionCompatibleRequest(List requests) { 096 // can always cancel saved or initiated document 097 if (routeHeader.isStateInitiated() || routeHeader.isStateSaved()) { 098 return true; 099 } 100 101 boolean actionCompatible = false; 102 Iterator ars = requests.iterator(); 103 ActionRequestValue actionRequest = null; 104 105 while (ars.hasNext()) { 106 actionRequest = (ActionRequestValue) ars.next(); 107 String request = actionRequest.getActionRequested(); 108 109 // APPROVE request matches all but FYI and ACK 110 if ( (KEWConstants.ACTION_REQUEST_APPROVE_REQ.equals(request)) || 111 (KEWConstants.ACTION_REQUEST_COMPLETE_REQ.equals(request)) ) { 112 actionCompatible = true; 113 break; 114 } 115 } 116 117 return actionCompatible; 118 } 119 120 /** 121 * 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 122 * 123 * @throws InvalidActionTakenException 124 */ 125 public void recordAction() throws InvalidActionTakenException { 126 MDC.put("docId", getRouteHeader().getDocumentId()); 127 updateSearchableAttributesIfPossible(); 128 129 LOG.debug("Disapproving document : " + annotation); 130 131 List actionRequests = getActionRequestService().findAllValidRequests(getPrincipal().getPrincipalId(), getDocumentId(), KEWConstants.ACTION_REQUEST_COMPLETE_REQ); 132 LOG.debug("Checking to see if the action is legal"); 133 String errorMessage = validateActionRules(actionRequests); 134 if (!org.apache.commons.lang.StringUtils.isEmpty(errorMessage)) { 135 throw new InvalidActionTakenException(errorMessage); 136 } 137 138 LOG.debug("Record the disapproval action"); 139 Recipient delegator = findDelegatorForActionRequests(actionRequests); 140 ActionTakenValue actionTaken = saveActionTaken(delegator); 141 142 LOG.debug("Deactivate all pending action requests"); 143 actionRequests = getActionRequestService().findPendingByDoc(getDocumentId()); 144 getActionRequestService().deactivateRequests(actionTaken, actionRequests); 145 notifyActionTaken(actionTaken); 146 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 generateNotifications(notificationNodeInstance); 154 155 LOG.debug("Disapproving document"); 156 try { 157 String oldStatus = getRouteHeader().getDocRouteStatus(); 158 routeHeader.markDocumentDisapproved(); 159 String newStatus = getRouteHeader().getDocRouteStatus(); 160 KEWServiceLocator.getRouteHeaderService().saveRouteHeader(routeHeader); 161 notifyStatusChange(newStatus, oldStatus); 162 } catch (WorkflowException ex) { 163 LOG.warn(ex, ex); 164 throw new InvalidActionTakenException(ex.getMessage()); 165 } 166 } 167 168 //generate notifications to all people that have approved the document including the initiator 169 private void generateNotifications(RouteNodeInstance notificationNodeInstance) 170 { 171 String groupName = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString( 172 KEWConstants.KEW_NAMESPACE, 173 KNSConstants.DetailTypes.WORKGROUP_DETAIL_TYPE, 174 KEWConstants.NOTIFICATION_EXCLUDED_USERS_WORKGROUP_NAME_IND); 175 176 Set<String> systemPrincipalIds = new HashSet<String>(); 177 178 if( !StringUtils.isBlank(groupName)) 179 { 180 Group systemUserWorkgroup = KimApiServiceLocator.getIdentityManagementService(). 181 getGroupByName(Utilities.parseGroupNamespaceCode(groupName), Utilities.parseGroupName(groupName)); 182 183 List<String> principalIds = KimApiServiceLocator. 184 getIdentityManagementService().getGroupMemberPrincipalIds( systemUserWorkgroup.getId()); 185 186 if (systemUserWorkgroup != null) 187 { 188 for( String id : principalIds) 189 { 190 systemPrincipalIds.add(id); 191 } 192 } 193 } 194 ActionRequestFactory arFactory = new ActionRequestFactory(getRouteHeader(), notificationNodeInstance); 195 Collection<ActionTakenValue> actions = KEWServiceLocator.getActionTakenService().findByDocumentId(getDocumentId()); 196 //one notification per person 197 Set<String> usersNotified = new HashSet<String>(); 198 for (ActionTakenValue action : actions) 199 { 200 if ((action.isApproval() || action.isCompletion()) && !usersNotified.contains(action.getPrincipalId())) 201 { 202 if (!systemPrincipalIds.contains(action.getPrincipalId())) 203 { 204 ActionRequestValue request = arFactory.createNotificationRequest(KEWConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, action.getPrincipal(), getActionTakenCode(), getPrincipal(), getActionTakenCode()); 205 KEWServiceLocator.getActionRequestService().activateRequest(request); 206 usersNotified.add(request.getPrincipalId()); 207 } 208 } 209 } 210 211 } 212 }