001 /** 002 * Copyright 2005-2014 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.rice.kew.actionrequest.service.impl; 017 018 import java.util.ArrayList; 019 import java.util.Collections; 020 import java.util.LinkedList; 021 import java.util.List; 022 023 import org.apache.commons.collections.CollectionUtils; 024 import org.apache.commons.collections.Predicate; 025 import org.kuali.rice.kew.actionitem.ActionItem; 026 import org.kuali.rice.kew.actionrequest.ActionRequestValue; 027 import org.kuali.rice.kew.api.action.ActionRequest; 028 import org.kuali.rice.kew.api.action.RecipientType; 029 import org.kuali.rice.kew.engine.node.NodeState; 030 import org.kuali.rice.kew.engine.node.RouteNodeInstance; 031 import org.kuali.rice.kew.service.KEWServiceLocator; 032 033 /** 034 * This utility class encapsulates functions used to provide notification suppression 035 * 036 * @author Kuali Rice Team (rice.collab@kuali.org) 037 * 038 */ 039 public class NotificationSuppression { 040 041 public static final String SUPPRESS_NOTIFY_KEY_START = "SuppressNotify"; 042 043 /** 044 * add metadata (a NodeState) to the route node so that if this action request is regenerated 045 * verbatim, the notification email will suppressed (since it is a duplicate!). 046 * @param nodeInstance where additional NodeState will be added 047 * @param actionRequestValue 048 */ 049 public void addNotificationSuppression( 050 RouteNodeInstance nodeInstance, ActionRequestValue actionRequestValue) { 051 052 // iterative depth first traversal of the action request tree 053 LinkedList<ActionRequestValue> stack = new LinkedList<ActionRequestValue>(); 054 // push 055 stack.add(actionRequestValue); 056 057 while (stack.size() > 0) { 058 // pop our next action request 059 ActionRequestValue childActionRequest = stack.removeLast(); 060 061 // process this action request only if it is a leaf 062 if (childActionRequest.getChildrenRequests() == null || 063 childActionRequest.getChildrenRequests().size() == 0) { 064 List<String> requestKeys = getSuppressNotifyNodeStateKeys(childActionRequest); 065 if (requestKeys != null) for (String requestKey : requestKeys) { 066 if (nodeInstance.getNodeState(requestKey) == null) { // only add once 067 NodeState ns = new NodeState(); 068 ns.setKey(requestKey); 069 ns.setValue("notification suppression"); 070 nodeInstance.addNodeState(ns); 071 } 072 } 073 } 074 075 // put child action requests on the stack 076 if (childActionRequest.getChildrenRequests() != null) { 077 // equivalent to 'push' all 078 stack.addAll(childActionRequest.getChildrenRequests()); 079 } 080 } 081 } 082 083 /** 084 * This method filters any ActionItems whose related ActionRequestValueS have been flagged for notification 085 * suppression. 086 * 087 * @param actionItems the ActionItemS to filter 088 * @param routeNodeInstance the RouteNodeInstance that the actionItems are associated with 089 */ 090 protected void filterNotificationSuppressedActionItems(List<ActionItem> actionItems, 091 final RouteNodeInstance routeNodeInstance) { 092 093 // remove all actionItems from the collection whose request has a suppress notification node state element 094 CollectionUtils.filter(actionItems, new Predicate() { 095 public boolean evaluate(Object object) { 096 boolean result = true; 097 ActionItem actionItem = (ActionItem)object; 098 ActionRequestValue actionRequest = 099 KEWServiceLocator.getActionRequestService().findByActionRequestId(actionItem.getActionRequestId()); 100 101 List<String> suppressNotificationKeys = getSuppressNotifyNodeStateKeys(actionRequest); 102 if (suppressNotificationKeys != null && suppressNotificationKeys.size() > 0) { 103 // if any of the keys are not present, we need to notify 104 boolean containsAll = true; 105 for (String key : suppressNotificationKeys) { 106 if (routeNodeInstance.getNodeState(key) == null) { 107 containsAll = false; 108 break; 109 } 110 } 111 // actionItem will be filtered if this Predicate returns false 112 result = !containsAll; // only filters if all keys are present 113 } 114 return result; 115 } 116 }); 117 } 118 119 /** 120 * 121 * <p>This method takes care of notification for ActionItemS. It has logic for suppressing notifications 122 * when the RouteNodeInstance has NodeState specifically hinting for notification suppression for a given 123 * ActionItem. 124 * 125 * <p>A side effect is that any notification suppression NodeStateS will be removed 126 * from the RouteNodeInstance after notifications are sent. 127 * 128 * @param actionItems a list of ActionItemS related to the given routeNodeInstance 129 * @param routeNodeInstance the RouteNodeInstance related to the given actionItems 130 */ 131 public void notify(List<ActionItem> actionItems, RouteNodeInstance routeNodeInstance) { 132 133 if (actionItems != null && actionItems.size() > 0) { 134 actionItems = new ArrayList<ActionItem>(actionItems); // defensive copy since we will filter 135 filterNotificationSuppressedActionItems(actionItems, routeNodeInstance); 136 // notify for any actionItems that were not filtered 137 if (actionItems.size() > 0) { 138 KEWServiceLocator.getNotificationService().notify(ActionItem.to(actionItems)); 139 } 140 deleteNotificationSuppression(routeNodeInstance); 141 } 142 } 143 144 /** 145 * This method removes all NodeStates related to notification suppression, saving the RouteNodeInstance if there 146 * were any removed. 147 * 148 * @param routeNodeInstance 149 */ 150 @SuppressWarnings("unchecked") 151 private void deleteNotificationSuppression( 152 final RouteNodeInstance routeNodeInstance) { 153 // remove all suppress notification node states 154 List<NodeState> nodeStates = routeNodeInstance.getState(); 155 if (nodeStates != null && nodeStates.size() > 0) { 156 List<String> nodeStateKeysToRemove = new ArrayList<String>(nodeStates.size()); 157 158 for (NodeState nodeState : nodeStates) { 159 if (nodeState.getKey().startsWith(NotificationSuppression.SUPPRESS_NOTIFY_KEY_START)) { 160 nodeStateKeysToRemove.add(nodeState.getKey()); 161 } 162 } 163 if (nodeStateKeysToRemove.size() > 0) { 164 for (String nodeStateKeyToRemove : nodeStateKeysToRemove) { 165 routeNodeInstance.removeNodeState(nodeStateKeyToRemove); 166 } 167 KEWServiceLocator.getRouteNodeService().save(routeNodeInstance); 168 } 169 } 170 } 171 172 173 /** 174 * Builds keys for action requests used for notification suppression. 175 * <p>NOTE: This method needs to stay in sync with {@link #getSuppressNotifyNodeStateKeys(org.kuali.rice.kew.dto.ActionRequestDTO)} 176 * Any changes here must be made there as well! 177 * @param a 178 * @return List 179 */ 180 protected List<String> getSuppressNotifyNodeStateKeys(ActionRequest a) { 181 List<String> results = Collections.emptyList(); 182 if (a != null) { 183 results = new ArrayList<String>(3); 184 addSuppressNotifyNodeStateKey(results, RecipientType.PRINCIPAL.getCode(), a.getPrincipalId()); 185 addSuppressNotifyNodeStateKey(results, RecipientType.GROUP.getCode(), a.getGroupId()); 186 addSuppressNotifyNodeStateKey(results, RecipientType.ROLE.getCode(), a.getQualifiedRoleName()); 187 } 188 return results; 189 } 190 191 /** 192 * Builds keys for action requests used for notification suppression. 193 * <p>NOTE: This method needs to stay in sync with {@link #getSuppressNotifyNodeStateKeys(org.kuali.rice.kew.actionrequest.ActionRequestValue)} 194 * Any changes here must be made there as well! 195 * @param a 196 * @return List 197 */ 198 protected List<String> getSuppressNotifyNodeStateKeys(ActionRequestValue a) { 199 List<String> results = Collections.emptyList(); 200 if (a != null) { 201 results = new ArrayList<String>(3); 202 addSuppressNotifyNodeStateKey(results, RecipientType.PRINCIPAL.getCode(), a.getPrincipalId()); 203 addSuppressNotifyNodeStateKey(results, RecipientType.GROUP.getCode(), a.getGroupId()); 204 addSuppressNotifyNodeStateKey(results, RecipientType.ROLE.getCode(), a.getQualifiedRoleName()); 205 } 206 return results; 207 } 208 209 210 /** 211 * This method adds a suppress notify key to the passed in list 212 * 213 * @param results the list that the key will be added to 214 * @param responsiblePartyType 215 * @param responsiblePartyId 216 */ 217 private void addSuppressNotifyNodeStateKey(List<String> results, String responsiblePartyType, 218 String responsiblePartyId) { 219 if (responsiblePartyId != null && responsiblePartyType != null) { 220 StringBuilder sb = new StringBuilder(SUPPRESS_NOTIFY_KEY_START); 221 sb.append("("); 222 sb.append(responsiblePartyType); 223 sb.append(","); 224 sb.append(responsiblePartyId); 225 sb.append(")"); 226 results.add(sb.toString()); 227 } 228 } 229 230 }