View Javadoc

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  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      	LinkedList<ActionRequestValue> stack = new LinkedList<ActionRequestValue>();
55      	// push
56      	stack.add(actionRequestValue);
57  
58      	while (stack.size() > 0) {
59      		// pop our next action request 
60      		ActionRequestValue childActionRequest = stack.removeLast(); 
61  
62      		// process this action request only if it is a leaf
63      		if (childActionRequest.getChildrenRequests() == null || 
64      				childActionRequest.getChildrenRequests().size() == 0) {
65      			List<String> requestKeys = getSuppressNotifyNodeStateKeys(childActionRequest);
66      			if (requestKeys != null) for (String requestKey : requestKeys) { 
67      				if (nodeInstance.getNodeState(requestKey) == null) { // only add once
68      					nodeInstance.addNodeState(new NodeState(requestKey, "notification suppression"));
69      				}
70      			}
71      		}
72  
73      		// put child action requests on the stack
74      		if (childActionRequest.getChildrenRequests() != null) {
75      			// equivalent to 'push' all
76      			stack.addAll(childActionRequest.getChildrenRequests());
77      		}
78      	}
79      }
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  		CollectionUtils.filter(actionItems, new Predicate() {
93  			public boolean evaluate(Object object) {
94  				boolean result = true;
95  				ActionItem actionItem = (ActionItem)object;
96  				ActionRequestValue actionRequest = 
97  					KEWServiceLocator.getActionRequestService().findByActionRequestId(actionItem.getActionRequestId());
98  				
99  				List<String> suppressNotificationKeys = getSuppressNotifyNodeStateKeys(actionRequest);
100 				if (suppressNotificationKeys != null && suppressNotificationKeys.size() > 0) {
101 					// if any of the keys are not present, we need to notify
102 					boolean containsAll = true;
103 					for (String key : suppressNotificationKeys) {
104 						if (routeNodeInstance.getNodeState(key) == null) {
105 							containsAll = false;
106 							break;
107 						}
108 					}
109 					// actionItem will be filtered if this Predicate returns false
110 					result = !containsAll; // only filters if all keys are present
111 				}
112 				return result;
113 			}
114 		});
115 	}
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 		if (actionItems != null && actionItems.size() > 0) {
132 			actionItems = new ArrayList<ActionItem>(actionItems); // defensive copy since we will filter
133 			filterNotificationSuppressedActionItems(actionItems, routeNodeInstance);
134 			// notify for any actionItems that were not filtered
135 			if (actionItems.size() > 0) { KEWServiceLocator.getNotificationService().notify(actionItems); }
136 			deleteNotificationSuppression(routeNodeInstance);
137 		}
138 	}
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 		List<NodeState> nodeStates = routeNodeInstance.getState();
151 		if (nodeStates != null && nodeStates.size() > 0) {
152 			List<String> nodeStateKeysToRemove = new ArrayList<String>(nodeStates.size());
153 
154 			for (NodeState nodeState : nodeStates) {
155 				if (nodeState.getKey().startsWith(NotificationSuppression.SUPPRESS_NOTIFY_KEY_START)) {
156 					nodeStateKeysToRemove.add(nodeState.getKey());
157 				}
158 			}
159 			if (nodeStateKeysToRemove.size() > 0) {
160 				for (String nodeStateKeyToRemove : nodeStateKeysToRemove) {
161 					routeNodeInstance.removeNodeState(nodeStateKeyToRemove);
162 				}
163 				KEWServiceLocator.getRouteNodeService().save(routeNodeInstance);
164 			}
165 		}
166 	}
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 		List<String> results = Collections.emptyList(); 
178 		if (a != null) {
179 			results = new ArrayList<String>(3);
180 			addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_USER_RECIPIENT_CD, a.getPrincipalId());
181 			addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_GROUP_RECIPIENT_CD, a.getGroupId());
182 			addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_ROLE_RECIPIENT_CD, a.getQualifiedRoleName());
183 		}
184 		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 		List<String> results = Collections.emptyList(); 
196 		if (a != null) {
197 			results = new ArrayList<String>(3);
198 			addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_USER_RECIPIENT_CD, a.getPrincipalId());
199 			addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_GROUP_RECIPIENT_CD, a.getGroupId());
200 			addSuppressNotifyNodeStateKey(results, KEWConstants.ACTION_REQUEST_ROLE_RECIPIENT_CD, a.getQualifiedRoleName());
201 		}
202 		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 		if (responsiblePartyId != null && responsiblePartyType != null) {
216 			StringBuilder sb = new StringBuilder(SUPPRESS_NOTIFY_KEY_START);
217 			sb.append("(");
218 			sb.append(responsiblePartyType);
219 			sb.append(",");
220 			sb.append(responsiblePartyId);
221 			sb.append(")");
222 			results.add(sb.toString());
223 		}
224 	}
225 	
226 }