Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
NotificationSuppression |
|
| 4.0;4 | ||||
NotificationSuppression$1 |
|
| 4.0;4 |
1 | /* | |
2 | * Copyright 2007-2009 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.dto.ActionRequestDTO; | |
28 | import org.kuali.rice.kew.engine.RouteContext; | |
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 | import org.kuali.rice.kew.util.KEWConstants; | |
33 | ||
34 | /** | |
35 | * This utility class encapsulates functions used to provide notification suppression | |
36 | * | |
37 | * @author Kuali Rice Team (rice.collab@kuali.org) | |
38 | * | |
39 | */ | |
40 | 0 | public class NotificationSuppression { |
41 | ||
42 | public static final String SUPPRESS_NOTIFY_KEY_START = "SuppressNotify"; | |
43 | ||
44 | /** | |
45 | * add metadata (a NodeState) to the route node so that if this action request is regenerated | |
46 | * verbatim, the notification email will suppressed (since it is a duplicate!). | |
47 | * @param nodeInstance where additional NodeState will be added | |
48 | * @param actionRequestValue | |
49 | */ | |
50 | public void addNotificationSuppression( | |
51 | RouteNodeInstance nodeInstance, ActionRequestValue actionRequestValue) { | |
52 | ||
53 | // iterative depth first traversal of the action request tree | |
54 | 0 | LinkedList<ActionRequestValue> stack = new LinkedList<ActionRequestValue>(); |
55 | // push | |
56 | 0 | stack.add(actionRequestValue); |
57 | ||
58 | 0 | while (stack.size() > 0) { |
59 | // pop our next action request | |
60 | 0 | ActionRequestValue childActionRequest = stack.removeLast(); |
61 | ||
62 | // process this action request only if it is a leaf | |
63 | 0 | if (childActionRequest.getChildrenRequests() == null || |
64 | childActionRequest.getChildrenRequests().size() == 0) { | |
65 | 0 | List<String> requestKeys = getSuppressNotifyNodeStateKeys(childActionRequest); |
66 | 0 | if (requestKeys != null) for (String requestKey : requestKeys) { |
67 | 0 | if (nodeInstance.getNodeState(requestKey) == null) { // only add once |
68 | 0 | NodeState ns = new NodeState(); |
69 | 0 | ns.setKey(requestKey); |
70 | 0 | ns.setValue("notification suppression"); |
71 | 0 | nodeInstance.addNodeState(ns); |
72 | 0 | } |
73 | } | |
74 | } | |
75 | ||
76 | // put child action requests on the stack | |
77 | 0 | if (childActionRequest.getChildrenRequests() != null) { |
78 | // equivalent to 'push' all | |
79 | 0 | stack.addAll(childActionRequest.getChildrenRequests()); |
80 | } | |
81 | 0 | } |
82 | 0 | } |
83 | ||
84 | /** | |
85 | * This method filters any ActionItems whose related ActionRequestValueS have been flagged for notification | |
86 | * suppression. | |
87 | * | |
88 | * @param actionItems the ActionItemS to filter | |
89 | * @param routeNodeInstance the RouteNodeInstance that the actionItems are associated with | |
90 | */ | |
91 | protected void filterNotificationSuppressedActionItems(List<ActionItem> actionItems, | |
92 | final RouteNodeInstance routeNodeInstance) { | |
93 | ||
94 | // remove all actionItems from the collection whose request has a suppress notification node state element | |
95 | 0 | CollectionUtils.filter(actionItems, new Predicate() { |
96 | public boolean evaluate(Object object) { | |
97 | 0 | boolean result = true; |
98 | 0 | ActionItem actionItem = (ActionItem)object; |
99 | 0 | ActionRequestValue actionRequest = |
100 | KEWServiceLocator.getActionRequestService().findByActionRequestId(actionItem.getActionRequestId()); | |
101 | ||
102 | 0 | List<String> suppressNotificationKeys = getSuppressNotifyNodeStateKeys(actionRequest); |
103 | 0 | if (suppressNotificationKeys != null && suppressNotificationKeys.size() > 0) { |
104 | // if any of the keys are not present, we need to notify | |
105 | 0 | boolean containsAll = true; |
106 | 0 | for (String key : suppressNotificationKeys) { |
107 | 0 | if (routeNodeInstance.getNodeState(key) == null) { |
108 | 0 | containsAll = false; |
109 | 0 | break; |
110 | } | |
111 | } | |
112 | // actionItem will be filtered if this Predicate returns false | |
113 | 0 | result = !containsAll; // only filters if all keys are present |
114 | } | |
115 | 0 | return result; |
116 | } | |
117 | }); | |
118 | 0 | } |
119 | ||
120 | /** | |
121 | * | |
122 | * <p>This method takes care of notification for ActionItemS. It has logic for suppressing notifications | |
123 | * when the RouteNodeInstance has NodeState specifically hinting for notification suppression for a given | |
124 | * ActionItem. | |
125 | * | |
126 | * <p>A side effect is that any notification suppression NodeStateS will be removed | |
127 | * from the RouteNodeInstance after notifications are sent. | |
128 | * | |
129 | * @param actionItems a list of ActionItemS related to the given routeNodeInstance | |
130 | * @param routeNodeInstance the RouteNodeInstance related to the given actionItems | |
131 | */ | |
132 | public void notify(List<ActionItem> actionItems, RouteNodeInstance routeNodeInstance) { | |
133 | ||
134 | 0 | if (actionItems != null && actionItems.size() > 0) { |
135 | 0 | actionItems = new ArrayList<ActionItem>(actionItems); // defensive copy since we will filter |
136 | 0 | filterNotificationSuppressedActionItems(actionItems, routeNodeInstance); |
137 | // notify for any actionItems that were not filtered | |
138 | 0 | if (actionItems.size() > 0) { KEWServiceLocator.getNotificationService().notify(actionItems); } |
139 | 0 | deleteNotificationSuppression(routeNodeInstance); |
140 | } | |
141 | 0 | } |
142 | ||
143 | /** | |
144 | * This method removes all NodeStates related to notification suppression, saving the RouteNodeInstance if there | |
145 | * were any removed. | |
146 | * | |
147 | * @param routeNodeInstance | |
148 | */ | |
149 | @SuppressWarnings("unchecked") | |
150 | private void deleteNotificationSuppression( | |
151 | final RouteNodeInstance routeNodeInstance) { | |
152 | // remove all suppress notification node states | |
153 | 0 | List<NodeState> nodeStates = routeNodeInstance.getState(); |
154 | 0 | if (nodeStates != null && nodeStates.size() > 0) { |
155 | 0 | List<String> nodeStateKeysToRemove = new ArrayList<String>(nodeStates.size()); |
156 | ||
157 | 0 | for (NodeState nodeState : nodeStates) { |
158 | 0 | if (nodeState.getKey().startsWith(NotificationSuppression.SUPPRESS_NOTIFY_KEY_START)) { |
159 | 0 | nodeStateKeysToRemove.add(nodeState.getKey()); |
160 | } | |
161 | } | |
162 | 0 | if (nodeStateKeysToRemove.size() > 0) { |
163 | 0 | for (String nodeStateKeyToRemove : nodeStateKeysToRemove) { |
164 | 0 | routeNodeInstance.removeNodeState(nodeStateKeyToRemove); |
165 | } | |
166 | 0 | KEWServiceLocator.getRouteNodeService().save(routeNodeInstance); |
167 | } | |
168 | } | |
169 | 0 | } |
170 | ||
171 | ||
172 | /** | |
173 | * Builds keys for action requests used for notification suppression. | |
174 | * <p>NOTE: This method needs to stay in sync with {@link #getSuppressNotifyNodeStateKeys(org.kuali.rice.kew.dto.ActionRequestDTO)} | |
175 | * Any changes here must be made there as well! | |
176 | * @param a | |
177 | * @return List | |
178 | */ | |
179 | protected List<String> getSuppressNotifyNodeStateKeys(ActionRequestDTO a) { | |
180 | 0 | List<String> results = Collections.emptyList(); |
181 | 0 | if (a != null) { |
182 | 0 | results = new ArrayList<String>(3); |
183 | 0 | addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_USER_RECIPIENT_CD, a.getPrincipalId()); |
184 | 0 | addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_GROUP_RECIPIENT_CD, a.getGroupId()); |
185 | 0 | addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_ROLE_RECIPIENT_CD, a.getQualifiedRoleName()); |
186 | } | |
187 | 0 | return results; |
188 | } | |
189 | ||
190 | /** | |
191 | * Builds keys for action requests used for notification suppression. | |
192 | * <p>NOTE: This method needs to stay in sync with {@link #getSuppressNotifyNodeStateKeys(org.kuali.rice.kew.actionrequest.ActionRequestValue)} | |
193 | * Any changes here must be made there as well! | |
194 | * @param a | |
195 | * @return List | |
196 | */ | |
197 | protected List<String> getSuppressNotifyNodeStateKeys(ActionRequestValue a) { | |
198 | 0 | List<String> results = Collections.emptyList(); |
199 | 0 | if (a != null) { |
200 | 0 | results = new ArrayList<String>(3); |
201 | 0 | addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_USER_RECIPIENT_CD, a.getPrincipalId()); |
202 | 0 | addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_GROUP_RECIPIENT_CD, a.getGroupId()); |
203 | 0 | addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_ROLE_RECIPIENT_CD, a.getQualifiedRoleName()); |
204 | } | |
205 | 0 | return results; |
206 | } | |
207 | ||
208 | ||
209 | /** | |
210 | * This method adds a suppress notify key to the passed in list | |
211 | * | |
212 | * @param results the list that the key will be added to | |
213 | * @param responsiblePartyType | |
214 | * @param responsiblePartyId | |
215 | */ | |
216 | private void addSuppressNotifyNodeStateKey(List<String> results, String responsiblePartyType, | |
217 | String responsiblePartyId) { | |
218 | 0 | if (responsiblePartyId != null && responsiblePartyType != null) { |
219 | 0 | StringBuilder sb = new StringBuilder(SUPPRESS_NOTIFY_KEY_START); |
220 | 0 | sb.append("("); |
221 | 0 | sb.append(responsiblePartyType); |
222 | 0 | sb.append(","); |
223 | 0 | sb.append(responsiblePartyId); |
224 | 0 | sb.append(")"); |
225 | 0 | results.add(sb.toString()); |
226 | } | |
227 | 0 | } |
228 | ||
229 | } |