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.group.Group; 034 import org.kuali.rice.kim.api.identity.principal.PrincipalContract; 035 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 036 import org.kuali.rice.krad.util.KRADConstants; 037 038 import java.util.Collection; 039 import java.util.HashSet; 040 import java.util.Iterator; 041 import java.util.List; 042 import 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 */ 051 public 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(KEWConstants.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(KEWConstants.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, KEWConstants.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 ( (KEWConstants.ACTION_REQUEST_APPROVE_REQ.equals(request)) || 110 (KEWConstants.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 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(), KEWConstants.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 LOG.debug("Sending Acknowledgements to all previous approvers/completers"); 147 // Generate the notification requests in the first node we find that the current user has an approve request 148 RouteNodeInstance notificationNodeInstance = null; 149 // if (actionRequests.size() > 0) { //I don't see why this matters let me know if it does rk 150 notificationNodeInstance = ((ActionRequestValue)actionRequests.get(0)).getNodeInstance(); 151 // } 152 generateNotifications(notificationNodeInstance); 153 154 LOG.debug("Disapproving document"); 155 try { 156 String oldStatus = getRouteHeader().getDocRouteStatus(); 157 routeHeader.markDocumentDisapproved(); 158 String newStatus = getRouteHeader().getDocRouteStatus(); 159 KEWServiceLocator.getRouteHeaderService().saveRouteHeader(routeHeader); 160 notifyStatusChange(newStatus, oldStatus); 161 } catch (WorkflowException ex) { 162 LOG.warn(ex, ex); 163 throw new InvalidActionTakenException(ex.getMessage()); 164 } 165 } 166 167 //generate notifications to all people that have approved the document including the initiator 168 private void generateNotifications(RouteNodeInstance notificationNodeInstance) 169 { 170 String groupName = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString( 171 KEWConstants.KEW_NAMESPACE, 172 KRADConstants.DetailTypes.WORKGROUP_DETAIL_TYPE, 173 KEWConstants.NOTIFICATION_EXCLUDED_USERS_WORKGROUP_NAME_IND); 174 175 Set<String> systemPrincipalIds = new HashSet<String>(); 176 177 if( !StringUtils.isBlank(groupName)) 178 { 179 Group systemUserWorkgroup = KimApiServiceLocator.getGroupService(). 180 getGroupByName(Utilities.parseGroupNamespaceCode(groupName), Utilities.parseGroupName(groupName)); 181 182 List<String> principalIds = KimApiServiceLocator. 183 getGroupService().getMemberPrincipalIds( systemUserWorkgroup.getId()); 184 185 if (systemUserWorkgroup != null) 186 { 187 for( String id : principalIds) 188 { 189 systemPrincipalIds.add(id); 190 } 191 } 192 } 193 ActionRequestFactory arFactory = new ActionRequestFactory(getRouteHeader(), notificationNodeInstance); 194 Collection<ActionTakenValue> actions = KEWServiceLocator.getActionTakenService().findByDocumentId(getDocumentId()); 195 //one notification per person 196 Set<String> usersNotified = new HashSet<String>(); 197 for (ActionTakenValue action : actions) 198 { 199 if ((action.isApproval() || action.isCompletion()) && !usersNotified.contains(action.getPrincipalId())) 200 { 201 if (!systemPrincipalIds.contains(action.getPrincipalId())) 202 { 203 ActionRequestValue request = arFactory.createNotificationRequest(KEWConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ, action.getPrincipal(), getActionTakenCode(), getPrincipal(), getActionTakenCode()); 204 KEWServiceLocator.getActionRequestService().activateRequest(request); 205 usersNotified.add(request.getPrincipalId()); 206 } 207 } 208 } 209 210 } 211 }