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 }