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 | nodeInstance.addNodeState(new NodeState(requestKey, "notification suppression")); |
69 | } | |
70 | } | |
71 | } | |
72 | ||
73 | // put child action requests on the stack | |
74 | 0 | if (childActionRequest.getChildrenRequests() != null) { |
75 | // equivalent to 'push' all | |
76 | 0 | stack.addAll(childActionRequest.getChildrenRequests()); |
77 | } | |
78 | 0 | } |
79 | 0 | } |
80 | ||
81 | /** | |
82 | * This method filters any ActionItems whose related ActionRequestValueS have been flagged for notification | |
83 | * suppression. | |
84 | * | |
85 | * @param actionItems the ActionItemS to filter | |
86 | * @param routeNodeInstance the RouteNodeInstance that the actionItems are associated with | |
87 | */ | |
88 | protected void filterNotificationSuppressedActionItems(List<ActionItem> actionItems, | |
89 | final RouteNodeInstance routeNodeInstance) { | |
90 | ||
91 | // remove all actionItems from the collection whose request has a suppress notification node state element | |
92 | 0 | CollectionUtils.filter(actionItems, new Predicate() { |
93 | public boolean evaluate(Object object) { | |
94 | 0 | boolean result = true; |
95 | 0 | ActionItem actionItem = (ActionItem)object; |
96 | 0 | ActionRequestValue actionRequest = |
97 | KEWServiceLocator.getActionRequestService().findByActionRequestId(actionItem.getActionRequestId()); | |
98 | ||
99 | 0 | List<String> suppressNotificationKeys = getSuppressNotifyNodeStateKeys(actionRequest); |
100 | 0 | if (suppressNotificationKeys != null && suppressNotificationKeys.size() > 0) { |
101 | // if any of the keys are not present, we need to notify | |
102 | 0 | boolean containsAll = true; |
103 | 0 | for (String key : suppressNotificationKeys) { |
104 | 0 | if (routeNodeInstance.getNodeState(key) == null) { |
105 | 0 | containsAll = false; |
106 | 0 | break; |
107 | } | |
108 | } | |
109 | // actionItem will be filtered if this Predicate returns false | |
110 | 0 | result = !containsAll; // only filters if all keys are present |
111 | } | |
112 | 0 | return result; |
113 | } | |
114 | }); | |
115 | 0 | } |
116 | ||
117 | /** | |
118 | * | |
119 | * <p>This method takes care of notification for ActionItemS. It has logic for suppressing notifications | |
120 | * when the RouteNodeInstance has NodeState specifically hinting for notification suppression for a given | |
121 | * ActionItem. | |
122 | * | |
123 | * <p>A side effect is that any notification suppression NodeStateS will be removed | |
124 | * from the RouteNodeInstance after notifications are sent. | |
125 | * | |
126 | * @param actionItems a list of ActionItemS related to the given routeNodeInstance | |
127 | * @param routeNodeInstance the RouteNodeInstance related to the given actionItems | |
128 | */ | |
129 | public void notify(List<ActionItem> actionItems, RouteNodeInstance routeNodeInstance) { | |
130 | ||
131 | 0 | if (actionItems != null && actionItems.size() > 0) { |
132 | 0 | actionItems = new ArrayList<ActionItem>(actionItems); // defensive copy since we will filter |
133 | 0 | filterNotificationSuppressedActionItems(actionItems, routeNodeInstance); |
134 | // notify for any actionItems that were not filtered | |
135 | 0 | if (actionItems.size() > 0) { KEWServiceLocator.getNotificationService().notify(actionItems); } |
136 | 0 | deleteNotificationSuppression(routeNodeInstance); |
137 | } | |
138 | 0 | } |
139 | ||
140 | /** | |
141 | * This method removes all NodeStates related to notification suppression, saving the RouteNodeInstance if there | |
142 | * were any removed. | |
143 | * | |
144 | * @param routeNodeInstance | |
145 | */ | |
146 | @SuppressWarnings("unchecked") | |
147 | private void deleteNotificationSuppression( | |
148 | final RouteNodeInstance routeNodeInstance) { | |
149 | // remove all suppress notification node states | |
150 | 0 | List<NodeState> nodeStates = routeNodeInstance.getState(); |
151 | 0 | if (nodeStates != null && nodeStates.size() > 0) { |
152 | 0 | List<String> nodeStateKeysToRemove = new ArrayList<String>(nodeStates.size()); |
153 | ||
154 | 0 | for (NodeState nodeState : nodeStates) { |
155 | 0 | if (nodeState.getKey().startsWith(NotificationSuppression.SUPPRESS_NOTIFY_KEY_START)) { |
156 | 0 | nodeStateKeysToRemove.add(nodeState.getKey()); |
157 | } | |
158 | } | |
159 | 0 | if (nodeStateKeysToRemove.size() > 0) { |
160 | 0 | for (String nodeStateKeyToRemove : nodeStateKeysToRemove) { |
161 | 0 | routeNodeInstance.removeNodeState(nodeStateKeyToRemove); |
162 | } | |
163 | 0 | KEWServiceLocator.getRouteNodeService().save(routeNodeInstance); |
164 | } | |
165 | } | |
166 | 0 | } |
167 | ||
168 | ||
169 | /** | |
170 | * Builds keys for action requests used for notification suppression. | |
171 | * <p>NOTE: This method needs to stay in sync with {@link #getSuppressNotifyNodeStateKeys(org.kuali.rice.kew.dto.ActionRequestDTO)} | |
172 | * Any changes here must be made there as well! | |
173 | * @param a | |
174 | * @return List | |
175 | */ | |
176 | protected List<String> getSuppressNotifyNodeStateKeys(ActionRequestDTO a) { | |
177 | 0 | List<String> results = Collections.emptyList(); |
178 | 0 | if (a != null) { |
179 | 0 | results = new ArrayList<String>(3); |
180 | 0 | addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_USER_RECIPIENT_CD, a.getPrincipalId()); |
181 | 0 | addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_GROUP_RECIPIENT_CD, a.getGroupId()); |
182 | 0 | addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_ROLE_RECIPIENT_CD, a.getQualifiedRoleName()); |
183 | } | |
184 | 0 | return results; |
185 | } | |
186 | ||
187 | /** | |
188 | * Builds keys for action requests used for notification suppression. | |
189 | * <p>NOTE: This method needs to stay in sync with {@link #getSuppressNotifyNodeStateKeys(org.kuali.rice.kew.actionrequest.ActionRequestValue)} | |
190 | * Any changes here must be made there as well! | |
191 | * @param a | |
192 | * @return List | |
193 | */ | |
194 | protected List<String> getSuppressNotifyNodeStateKeys(ActionRequestValue a) { | |
195 | 0 | List<String> results = Collections.emptyList(); |
196 | 0 | if (a != null) { |
197 | 0 | results = new ArrayList<String>(3); |
198 | 0 | addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_USER_RECIPIENT_CD, a.getPrincipalId()); |
199 | 0 | addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_GROUP_RECIPIENT_CD, a.getGroupId()); |
200 | 0 | addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_ROLE_RECIPIENT_CD, a.getQualifiedRoleName()); |
201 | } | |
202 | 0 | return results; |
203 | } | |
204 | ||
205 | ||
206 | /** | |
207 | * This method adds a suppress notify key to the passed in list | |
208 | * | |
209 | * @param results the list that the key will be added to | |
210 | * @param responsiblePartyType | |
211 | * @param responsiblePartyId | |
212 | */ | |
213 | private void addSuppressNotifyNodeStateKey(List<String> results, String responsiblePartyType, | |
214 | String responsiblePartyId) { | |
215 | 0 | if (responsiblePartyId != null && responsiblePartyType != null) { |
216 | 0 | StringBuilder sb = new StringBuilder(SUPPRESS_NOTIFY_KEY_START); |
217 | 0 | sb.append("("); |
218 | 0 | sb.append(responsiblePartyType); |
219 | 0 | sb.append(","); |
220 | 0 | sb.append(responsiblePartyId); |
221 | 0 | sb.append(")"); |
222 | 0 | results.add(sb.toString()); |
223 | } | |
224 | 0 | } |
225 | ||
226 | } |