| 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 | } |