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 }