Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
NotificationSuppression |
|
| 4.0;4 | ||||
NotificationSuppression$1 |
|
| 4.0;4 |
1 | /** | |
2 | * Copyright 2005-2011 The Kuali Foundation | |
3 | * | |
4 | * Licensed under the Educational Community License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at | |
7 | * | |
8 | * http://www.opensource.org/licenses/ecl2.php | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | package org.kuali.rice.kew.actionrequest.service.impl; | |
17 | ||
18 | import java.util.ArrayList; | |
19 | import java.util.Collections; | |
20 | import java.util.LinkedList; | |
21 | import java.util.List; | |
22 | ||
23 | import org.apache.commons.collections.CollectionUtils; | |
24 | import org.apache.commons.collections.Predicate; | |
25 | import org.kuali.rice.kew.actionitem.ActionItem; | |
26 | import org.kuali.rice.kew.actionrequest.ActionRequestValue; | |
27 | import org.kuali.rice.kew.api.action.ActionRequest; | |
28 | import org.kuali.rice.kew.api.action.RecipientType; | |
29 | import org.kuali.rice.kew.engine.node.NodeState; | |
30 | import org.kuali.rice.kew.engine.node.RouteNodeInstance; | |
31 | import org.kuali.rice.kew.service.KEWServiceLocator; | |
32 | ||
33 | /** | |
34 | * This utility class encapsulates functions used to provide notification suppression | |
35 | * | |
36 | * @author Kuali Rice Team (rice.collab@kuali.org) | |
37 | * | |
38 | */ | |
39 | 0 | public class NotificationSuppression { |
40 | ||
41 | public static final String SUPPRESS_NOTIFY_KEY_START = "SuppressNotify"; | |
42 | ||
43 | /** | |
44 | * add metadata (a NodeState) to the route node so that if this action request is regenerated | |
45 | * verbatim, the notification email will suppressed (since it is a duplicate!). | |
46 | * @param nodeInstance where additional NodeState will be added | |
47 | * @param actionRequestValue | |
48 | */ | |
49 | public void addNotificationSuppression( | |
50 | RouteNodeInstance nodeInstance, ActionRequestValue actionRequestValue) { | |
51 | ||
52 | // iterative depth first traversal of the action request tree | |
53 | 0 | LinkedList<ActionRequestValue> stack = new LinkedList<ActionRequestValue>(); |
54 | // push | |
55 | 0 | stack.add(actionRequestValue); |
56 | ||
57 | 0 | while (stack.size() > 0) { |
58 | // pop our next action request | |
59 | 0 | ActionRequestValue childActionRequest = stack.removeLast(); |
60 | ||
61 | // process this action request only if it is a leaf | |
62 | 0 | if (childActionRequest.getChildrenRequests() == null || |
63 | childActionRequest.getChildrenRequests().size() == 0) { | |
64 | 0 | List<String> requestKeys = getSuppressNotifyNodeStateKeys(childActionRequest); |
65 | 0 | if (requestKeys != null) for (String requestKey : requestKeys) { |
66 | 0 | if (nodeInstance.getNodeState(requestKey) == null) { // only add once |
67 | 0 | NodeState ns = new NodeState(); |
68 | 0 | ns.setKey(requestKey); |
69 | 0 | ns.setValue("notification suppression"); |
70 | 0 | nodeInstance.addNodeState(ns); |
71 | 0 | } |
72 | } | |
73 | } | |
74 | ||
75 | // put child action requests on the stack | |
76 | 0 | if (childActionRequest.getChildrenRequests() != null) { |
77 | // equivalent to 'push' all | |
78 | 0 | stack.addAll(childActionRequest.getChildrenRequests()); |
79 | } | |
80 | 0 | } |
81 | 0 | } |
82 | ||
83 | /** | |
84 | * This method filters any ActionItems whose related ActionRequestValueS have been flagged for notification | |
85 | * suppression. | |
86 | * | |
87 | * @param actionItems the ActionItemS to filter | |
88 | * @param routeNodeInstance the RouteNodeInstance that the actionItems are associated with | |
89 | */ | |
90 | protected void filterNotificationSuppressedActionItems(List<ActionItem> actionItems, | |
91 | final RouteNodeInstance routeNodeInstance) { | |
92 | ||
93 | // remove all actionItems from the collection whose request has a suppress notification node state element | |
94 | 0 | CollectionUtils.filter(actionItems, new Predicate() { |
95 | public boolean evaluate(Object object) { | |
96 | 0 | boolean result = true; |
97 | 0 | ActionItem actionItem = (ActionItem)object; |
98 | 0 | ActionRequestValue actionRequest = |
99 | KEWServiceLocator.getActionRequestService().findByActionRequestId(actionItem.getActionRequestId()); | |
100 | ||
101 | 0 | List<String> suppressNotificationKeys = getSuppressNotifyNodeStateKeys(actionRequest); |
102 | 0 | if (suppressNotificationKeys != null && suppressNotificationKeys.size() > 0) { |
103 | // if any of the keys are not present, we need to notify | |
104 | 0 | boolean containsAll = true; |
105 | 0 | for (String key : suppressNotificationKeys) { |
106 | 0 | if (routeNodeInstance.getNodeState(key) == null) { |
107 | 0 | containsAll = false; |
108 | 0 | break; |
109 | } | |
110 | } | |
111 | // actionItem will be filtered if this Predicate returns false | |
112 | 0 | result = !containsAll; // only filters if all keys are present |
113 | } | |
114 | 0 | return result; |
115 | } | |
116 | }); | |
117 | 0 | } |
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 | 0 | if (actionItems != null && actionItems.size() > 0) { |
134 | 0 | actionItems = new ArrayList<ActionItem>(actionItems); // defensive copy since we will filter |
135 | 0 | filterNotificationSuppressedActionItems(actionItems, routeNodeInstance); |
136 | // notify for any actionItems that were not filtered | |
137 | 0 | if (actionItems.size() > 0) { |
138 | 0 | KEWServiceLocator.getNotificationService().notify(ActionItem.to(actionItems)); |
139 | } | |
140 | 0 | deleteNotificationSuppression(routeNodeInstance); |
141 | } | |
142 | 0 | } |
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 | 0 | List<NodeState> nodeStates = routeNodeInstance.getState(); |
155 | 0 | if (nodeStates != null && nodeStates.size() > 0) { |
156 | 0 | List<String> nodeStateKeysToRemove = new ArrayList<String>(nodeStates.size()); |
157 | ||
158 | 0 | for (NodeState nodeState : nodeStates) { |
159 | 0 | if (nodeState.getKey().startsWith(NotificationSuppression.SUPPRESS_NOTIFY_KEY_START)) { |
160 | 0 | nodeStateKeysToRemove.add(nodeState.getKey()); |
161 | } | |
162 | } | |
163 | 0 | if (nodeStateKeysToRemove.size() > 0) { |
164 | 0 | for (String nodeStateKeyToRemove : nodeStateKeysToRemove) { |
165 | 0 | routeNodeInstance.removeNodeState(nodeStateKeyToRemove); |
166 | } | |
167 | 0 | KEWServiceLocator.getRouteNodeService().save(routeNodeInstance); |
168 | } | |
169 | } | |
170 | 0 | } |
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 | 0 | List<String> results = Collections.emptyList(); |
182 | 0 | if (a != null) { |
183 | 0 | results = new ArrayList<String>(3); |
184 | 0 | addSuppressNotifyNodeStateKey(results, RecipientType.PRINCIPAL.getCode(), a.getPrincipalId()); |
185 | 0 | addSuppressNotifyNodeStateKey(results, RecipientType.GROUP.getCode(), a.getGroupId()); |
186 | 0 | addSuppressNotifyNodeStateKey(results, RecipientType.ROLE.getCode(), a.getQualifiedRoleName()); |
187 | } | |
188 | 0 | 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 | 0 | List<String> results = Collections.emptyList(); |
200 | 0 | if (a != null) { |
201 | 0 | results = new ArrayList<String>(3); |
202 | 0 | addSuppressNotifyNodeStateKey(results, RecipientType.PRINCIPAL.getCode(), a.getPrincipalId()); |
203 | 0 | addSuppressNotifyNodeStateKey(results, RecipientType.GROUP.getCode(), a.getGroupId()); |
204 | 0 | addSuppressNotifyNodeStateKey(results, RecipientType.ROLE.getCode(), a.getQualifiedRoleName()); |
205 | } | |
206 | 0 | 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 | 0 | if (responsiblePartyId != null && responsiblePartyType != null) { |
220 | 0 | StringBuilder sb = new StringBuilder(SUPPRESS_NOTIFY_KEY_START); |
221 | 0 | sb.append("("); |
222 | 0 | sb.append(responsiblePartyType); |
223 | 0 | sb.append(","); |
224 | 0 | sb.append(responsiblePartyId); |
225 | 0 | sb.append(")"); |
226 | 0 | results.add(sb.toString()); |
227 | } | |
228 | 0 | } |
229 | ||
230 | } |