View Javadoc

1   /**
2    * Copyright 2005-2012 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;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.log4j.Logger;
21  import org.kuali.rice.core.api.delegation.DelegationType;
22  import org.kuali.rice.core.api.exception.RiceRuntimeException;
23  import org.kuali.rice.core.api.membership.MemberType;
24  import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
25  import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
26  import org.kuali.rice.kew.api.WorkflowRuntimeException;
27  import org.kuali.rice.kew.api.action.ActionRequestPolicy;
28  import org.kuali.rice.kew.api.action.ActionRequestStatus;
29  import org.kuali.rice.kew.api.action.RecipientType;
30  import org.kuali.rice.kew.api.identity.Id;
31  import org.kuali.rice.kew.api.user.UserId;
32  import org.kuali.rice.kew.api.util.CodeTranslator;
33  import org.kuali.rice.kew.engine.RouteContext;
34  import org.kuali.rice.kew.engine.node.RouteNodeInstance;
35  import org.kuali.rice.kew.identity.service.IdentityHelperService;
36  import org.kuali.rice.kew.role.KimRoleRecipient;
37  import org.kuali.rice.kew.role.KimRoleResponsibilityRecipient;
38  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
39  import org.kuali.rice.kew.rule.ResolvedQualifiedRole;
40  import org.kuali.rice.kew.service.KEWServiceLocator;
41  import org.kuali.rice.kew.user.RoleRecipient;
42  import org.kuali.rice.kew.api.KewApiConstants;
43  import org.kuali.rice.kew.util.Utilities;
44  import org.kuali.rice.kew.workgroup.GroupId;
45  import org.kuali.rice.kim.api.common.delegate.DelegateMember;
46  import org.kuali.rice.kim.api.common.delegate.DelegateType;
47  import org.kuali.rice.kim.api.group.Group;
48  import org.kuali.rice.kim.api.group.GroupService;
49  import org.kuali.rice.kim.api.identity.IdentityService;
50  import org.kuali.rice.kim.api.identity.principal.Principal;
51  import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
52  import org.kuali.rice.kim.api.responsibility.ResponsibilityAction;
53  import org.kuali.rice.kim.api.role.Role;
54  import org.kuali.rice.kim.api.role.RoleMembership;
55  import org.kuali.rice.kim.api.role.RoleService;
56  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
57  import org.kuali.rice.krad.util.KRADConstants;
58  import org.kuali.rice.krad.util.ObjectUtils;
59  
60  import java.sql.Timestamp;
61  import java.util.ArrayList;
62  import java.util.Collection;
63  import java.util.HashSet;
64  import java.util.Iterator;
65  import java.util.List;
66  import java.util.Map;
67  import java.util.Set;
68  
69  
70  /**
71   * A factory to aid in creating the ever-so-gnarly ActionRequestValue object.
72   *
73   * @author Kuali Rice Team (rice.collab@kuali.org)
74   */
75  public class ActionRequestFactory {
76  
77  	private static final Logger LOG = Logger.getLogger(ActionRequestFactory.class);
78  
79  	private static RoleService roleService;
80  	private static IdentityHelperService identityHelperService;
81  	private static IdentityService identityService;
82      private static GroupService groupService;
83  	private static ActionRequestService actionRequestService;
84  	
85  	private DocumentRouteHeaderValue document;
86  	private RouteNodeInstance routeNode;
87  	private List<ActionRequestValue> requestGraphs = new ArrayList<ActionRequestValue>();
88  
89  	public ActionRequestFactory() {
90  	}
91  
92  	public ActionRequestFactory(DocumentRouteHeaderValue document) {
93  		this.document = document;
94  	}
95  
96  	public ActionRequestFactory(DocumentRouteHeaderValue document, RouteNodeInstance routeNode) {
97  		this.document = document;
98  		this.routeNode = routeNode;
99  	}
100 
101     public ActionRequestFactory(RouteContext routeContext) {
102         this(routeContext.getDocument(), routeContext.getNodeInstance());
103     }
104 
105 	/**
106 	 * Constructs ActionRequestValue using default priority and 0 as responsibility
107 	 *
108 	 * @param actionRequested
109 	 * @param recipient
110 	 * @param description
111 	 * @param forceAction
112 	 * @param annotation
113      * @return ActionRequestValue
114 	 */
115 	public ActionRequestValue createActionRequest(String actionRequested, Recipient recipient, String description, Boolean forceAction, String annotation) {
116 		return createActionRequest(actionRequested, new Integer(0), recipient, description, KewApiConstants.MACHINE_GENERATED_RESPONSIBILITY_ID, forceAction, annotation);
117 	}
118 
119 	public ActionRequestValue createActionRequest(String actionRequested, Integer priority, Recipient recipient, String description, String responsibilityId, Boolean forceAction, String annotation) {
120     	return createActionRequest(actionRequested, priority, recipient, description, responsibilityId, forceAction, null, null, annotation);
121     }
122 
123 	public ActionRequestValue createActionRequest(String actionRequested, Integer priority, Recipient recipient, String description, String responsibilityId, Boolean forceAction, String approvePolicy, String ruleId, String annotation) {
124 		return createActionRequest(actionRequested, priority, recipient, description, responsibilityId, forceAction, approvePolicy, ruleId, annotation, null);
125 	}
126 
127 	public ActionRequestValue createActionRequest(String actionRequested, Integer priority, Recipient recipient, String description, String responsibilityId, Boolean forceAction, String approvePolicy, String ruleId, String annotation, String requestLabel) {
128 		ActionRequestValue actionRequest = new ActionRequestValue();
129         actionRequest.setActionRequested(actionRequested);
130         actionRequest.setDocVersion(document.getDocVersion());
131         actionRequest.setPriority(priority);
132         actionRequest.setRouteHeader(document);
133         actionRequest.setDocumentId(document.getDocumentId());
134         actionRequest.setRouteLevel(document.getDocRouteLevel());
135     	actionRequest.setNodeInstance(routeNode);
136     	actionRequest.setResponsibilityId(responsibilityId);
137     	actionRequest.setResponsibilityDesc(description);
138     	actionRequest.setApprovePolicy(approvePolicy);
139     	actionRequest.setForceAction(forceAction);
140     	actionRequest.setRuleBaseValuesId(ruleId);
141     	actionRequest.setAnnotation(annotation);
142     	actionRequest.setRequestLabel(requestLabel);
143     	setDefaultProperties(actionRequest);
144     	resolveRecipient(actionRequest, recipient);
145 
146         return actionRequest;
147 	}
148 
149 	public ActionRequestValue createBlankActionRequest() {
150 		ActionRequestValue request = new ActionRequestValue();
151 		request.setRouteHeader(document);
152 		if (document != null) {
153 			request.setDocumentId(document.getDocumentId());
154 		}
155 		request.setNodeInstance(routeNode);
156 		return request;
157 	}
158 
159 
160     public ActionRequestValue createNotificationRequest(String actionRequestCode, PrincipalContract principal, String reasonActionCode, PrincipalContract reasonActionUser, String responsibilityDesc) {
161     	ActionRequestValue request = createActionRequest(actionRequestCode, new KimPrincipalRecipient(principal), responsibilityDesc, Boolean.TRUE, null);
162     	String annotation = generateNotificationAnnotation(reasonActionUser, actionRequestCode, reasonActionCode, request);
163     	request.setAnnotation(annotation);
164     	return request;
165     }
166 
167     //unify these 2 methods if possible
168     public List<ActionRequestValue> generateNotifications(List requests, PrincipalContract principal, Recipient delegator,
169             String notificationRequestCode, String actionTakenCode)
170     {
171         String groupName =  CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE,
172                 KRADConstants.DetailTypes.WORKGROUP_DETAIL_TYPE,
173                 KewApiConstants.NOTIFICATION_EXCLUDED_USERS_WORKGROUP_NAME_IND);
174         
175         
176         Group notifyExclusionWorkgroup = null;
177         if(!StringUtils.isBlank(groupName)){
178         	notifyExclusionWorkgroup = getGroupService().getGroupByNameAndNamespaceCode(
179                     Utilities.parseGroupNamespaceCode(groupName), Utilities.parseGroupName(groupName));
180         }
181         
182  
183         
184         return generateNotifications(null, getActionRequestService().getRootRequests(requests), principal, delegator, notificationRequestCode, actionTakenCode, notifyExclusionWorkgroup);
185     }
186 
187     private List<ActionRequestValue> generateNotifications(ActionRequestValue parentRequest,
188             List requests, PrincipalContract principal, Recipient delegator, String notificationRequestCode,
189             String actionTakenCode, Group notifyExclusionWorkgroup)
190     {
191         List<ActionRequestValue> notificationRequests = new ArrayList<ActionRequestValue>();
192         for (Iterator iter = requests.iterator(); iter.hasNext();) 
193         {
194             ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
195             if (!(actionRequest.isRecipientRoutedRequest(principal.getPrincipalId()) || actionRequest.isRecipientRoutedRequest(delegator)))
196             {
197                 // skip user requests to system users
198                 if ((notifyExclusionWorkgroup != null) &&
199                         (isRecipientInGroup(notifyExclusionWorkgroup, actionRequest.getRecipient())))
200                 {
201                     continue;
202                 }
203                 ActionRequestValue notificationRequest = createNotificationRequest(actionRequest, principal, notificationRequestCode, actionTakenCode);
204                 notificationRequests.add(notificationRequest);
205                 if (parentRequest != null)
206                 {
207                     notificationRequest.setParentActionRequest(parentRequest);
208                     parentRequest.getChildrenRequests().add(notificationRequest);
209                 }
210                 notificationRequests.addAll(generateNotifications(notificationRequest, actionRequest.getChildrenRequests(), principal, delegator, notificationRequestCode, actionTakenCode, notifyExclusionWorkgroup));
211             }
212         }
213         return notificationRequests;
214     }
215 
216     private boolean isRecipientInGroup(Group group, Recipient recipient)
217     {
218         boolean isMember = false;
219 
220         if(recipient instanceof KimPrincipalRecipient)
221         {
222             String principalId = ((KimPrincipalRecipient) recipient).getPrincipalId();
223             String groupId = group.getId();
224             isMember = getGroupService().isMemberOfGroup(principalId, groupId);
225         }
226         else if (recipient instanceof KimGroupRecipient)
227         {
228             String kimRecipientId = ((KimGroupRecipient) recipient).getGroup().getId();
229             isMember = getGroupService().isGroupMemberOfGroup(kimRecipientId, group.getId() );
230         }
231         return isMember;
232     }
233 
234     private ActionRequestValue createNotificationRequest(ActionRequestValue actionRequest, PrincipalContract reasonPrincipal, String notificationRequestCode, String actionTakenCode) {
235 
236     	String annotation = generateNotificationAnnotation(reasonPrincipal, notificationRequestCode, actionTakenCode, actionRequest);
237         ActionRequestValue request = createActionRequest(notificationRequestCode, actionRequest.getPriority(), actionRequest.getRecipient(), actionRequest.getResponsibilityDesc(), KewApiConstants.MACHINE_GENERATED_RESPONSIBILITY_ID, Boolean.TRUE, annotation);
238 
239         request.setDocVersion(actionRequest.getDocVersion());
240         request.setApprovePolicy(actionRequest.getApprovePolicy());
241         request.setRoleName(actionRequest.getRoleName());
242         request.setQualifiedRoleName(actionRequest.getQualifiedRoleName());
243         request.setQualifiedRoleNameLabel(actionRequest.getQualifiedRoleNameLabel());
244         request.setDelegationType(actionRequest.getDelegationType());
245         return request;
246     }
247 
248     private void setDefaultProperties(ActionRequestValue actionRequest) {
249     	if (actionRequest.getApprovePolicy() == null) {
250     		actionRequest.setApprovePolicy(ActionRequestPolicy.FIRST.getCode());
251     	}
252         actionRequest.setCreateDate(new Timestamp(System.currentTimeMillis()));
253         actionRequest.setCurrentIndicator(Boolean.TRUE);
254         if (actionRequest.getForceAction() == null) {
255         	actionRequest.setForceAction(Boolean.FALSE);
256         }
257         if (routeNode != null) {
258         	actionRequest.setNodeInstance(routeNode);
259         }
260         actionRequest.setJrfVerNbr(new Integer(0));
261         actionRequest.setStatus(ActionRequestStatus.INITIALIZED.getCode());
262         actionRequest.setRouteHeader(document);
263         actionRequest.setDocumentId(document.getDocumentId());
264     }
265 
266     private static void resolveRecipient(ActionRequestValue actionRequest, Recipient recipient) {
267     	if (recipient instanceof KimPrincipalRecipient) {
268     		actionRequest.setRecipientTypeCd(RecipientType.PRINCIPAL.getCode());
269     		actionRequest.setPrincipalId(((KimPrincipalRecipient)recipient).getPrincipal().getPrincipalId());
270     	}  else if (recipient instanceof KimGroupRecipient) {
271     		KimGroupRecipient kimGroupRecipient = (KimGroupRecipient)recipient;
272     		actionRequest.setRecipientTypeCd(RecipientType.GROUP.getCode());
273     		actionRequest.setGroupId(kimGroupRecipient.getGroup().getId());
274     	} else if (recipient instanceof RoleRecipient){
275     		RoleRecipient role = (RoleRecipient)recipient;
276     		actionRequest.setRecipientTypeCd(RecipientType.ROLE.getCode());
277     		actionRequest.setRoleName(role.getRoleName());
278     		actionRequest.setQualifiedRoleName(role.getQualifiedRoleName());
279     		ResolvedQualifiedRole qualifiedRole = role.getResolvedQualifiedRole();
280     		if (qualifiedRole != null) {
281     			actionRequest.setAnnotation(qualifiedRole.getAnnotation() == null ? "" : qualifiedRole.getAnnotation());
282     			actionRequest.setQualifiedRoleNameLabel(qualifiedRole.getQualifiedRoleLabel());
283     		}
284     		Recipient targetRecipient = role.getTarget();
285     		if (role.getTarget() != null) {
286     			if (targetRecipient instanceof RoleRecipient) {
287     				throw new WorkflowRuntimeException("Role Cannot Target a role problem activating request for document " + actionRequest.getDocumentId());
288     			}
289     			resolveRecipient(actionRequest, role.getTarget());
290     		}
291     	} else if (recipient instanceof KimRoleResponsibilityRecipient) {
292     		KimRoleResponsibilityRecipient roleResponsibilityRecipient = (KimRoleResponsibilityRecipient)recipient;
293     		actionRequest.setRecipientTypeCd(RecipientType.ROLE.getCode());
294     		actionRequest.setRoleName(roleResponsibilityRecipient.getResponsibilities().get(0).getRoleId());
295     		actionRequest.setQualifiedRoleName(
296                     roleResponsibilityRecipient.getResponsibilities().get(0).getResponsibilityName());
297     		// what about qualified role name label?
298 //    		actionRequest.setAnnotation(roleRecipient.getResponsibilities().get(0).getResponsibilityName());
299     		Recipient targetRecipient = roleResponsibilityRecipient.getTarget();
300     		if (targetRecipient != null) {
301     			if (targetRecipient instanceof RoleRecipient) {
302     				throw new WorkflowRuntimeException("Role Cannot Target a role problem activating request for document " + actionRequest.getDocumentId());
303     			}
304     			resolveRecipient(actionRequest, roleResponsibilityRecipient.getTarget());
305     		}
306     	} else if (recipient instanceof KimRoleRecipient) {
307             KimRoleRecipient roleRecipient = (KimRoleRecipient)recipient;
308             actionRequest.setRecipientTypeCd(RecipientType.ROLE.getCode());
309             Role role = roleRecipient.getRole();
310             actionRequest.setRoleName(role.getId());
311             actionRequest.setQualifiedRoleNameLabel(role.getName());
312             Recipient targetRecipient = roleRecipient.getTarget();
313     		if (targetRecipient != null) {
314     			if (targetRecipient instanceof RoleRecipient) {
315     				throw new WorkflowRuntimeException("Role Cannot Target a role problem activating request for document " + actionRequest.getDocumentId());
316     			}
317     			resolveRecipient(actionRequest, targetRecipient);
318     		}
319         }
320     }
321 
322     /**
323      * Creates a root Role Request
324      * @param role
325      * @param actionRequested
326      * @param approvePolicy
327      * @param priority
328      * @param responsibilityId
329      * @param forceAction
330      * @param description
331      * @param ruleId
332      * @return the created root role request
333      */
334     public ActionRequestValue addRoleRequest(RoleRecipient role, String actionRequested, String approvePolicy, Integer priority, String responsibilityId, Boolean forceAction, String description, String ruleId) {
335 
336     	ActionRequestValue requestGraph = createActionRequest(actionRequested, priority, role, description, responsibilityId, forceAction, approvePolicy, ruleId, null);
337     	if (role != null && role.getResolvedQualifiedRole() != null && role.getResolvedQualifiedRole().getRecipients() != null) {
338     	    int legitimateTargets = 0;
339             for (Iterator<Id> iter = role.getResolvedQualifiedRole().getRecipients().iterator(); iter.hasNext();) {
340                 Id recipientId = (Id) iter.next();
341                 if (recipientId.isEmpty())
342                 {
343                     throw new WorkflowRuntimeException("Failed to resolve id of type " + recipientId.getClass().getName() + " returned from role '" + role.getRoleName() + "'.  Id returned contained a null or empty value.");
344                 }
345                 if (recipientId instanceof UserId)
346                 {
347                     Principal principal = getIdentityHelperService().getPrincipal((UserId) recipientId);
348                     if(ObjectUtils.isNotNull(principal)) {
349                         role.setTarget(new KimPrincipalRecipient(principal));
350                     }
351                 } else if (recipientId instanceof GroupId)
352                 {
353                     role.setTarget(new KimGroupRecipient(getIdentityHelperService().getGroup((GroupId) recipientId)));
354                 } else
355                 {
356                     throw new WorkflowRuntimeException("Could not process the given type of id: " + recipientId.getClass());
357                 }
358                 if (role.getTarget() != null)
359                 {
360                     legitimateTargets++;
361                     ActionRequestValue request = createActionRequest(actionRequested, priority, role, description, responsibilityId, forceAction, null, ruleId, null);
362                     request.setParentActionRequest(requestGraph);
363                     requestGraph.getChildrenRequests().add(request);
364                 }
365             }
366     	if (legitimateTargets == 0) {
367             LOG.warn("Role did not yield any legitimate recipients");
368         }
369     	} else {
370     		LOG.warn("Didn't create action requests for action request description '" + description + "' because of null role or null part of role object graph.");
371     	}
372     	requestGraphs.add(requestGraph);
373     	return requestGraph;
374     }
375 
376     /**
377      * Generates an ActionRequest graph for the given KIM Responsibilities.  This graph includes any associated delegations.
378      * @param responsibilities
379      * @param approvePolicy
380      */
381     public void addRoleResponsibilityRequest(List<ResponsibilityAction> responsibilities, String approvePolicy) {
382     	if (responsibilities == null || responsibilities.isEmpty()) {
383     		LOG.warn("Didn't create action requests for action request description because no responsibilities were defined.");
384     		return;
385     	}
386     	// it's assumed the that all in the list have the same action type code, priority number, etc.
387     	String actionTypeCode = responsibilities.get(0).getActionTypeCode();
388     	Integer priority = responsibilities.get(0).getPriorityNumber();
389     	boolean forceAction = responsibilities.get(0).isForceAction();
390     	KimRoleResponsibilityRecipient roleResponsibilityRecipient = new KimRoleResponsibilityRecipient(responsibilities);
391 
392     	// Creation of a parent graph entry for ????
393     	ActionRequestValue requestGraph = null;
394     	StringBuffer parentAnnotation = null;
395     	// set to allow for suppression of duplicate annotations on the parent action request
396     	Set<String> uniqueChildAnnotations = null;
397     	if ( responsibilities.size() > 1 ) {
398 	    	requestGraph = createActionRequest(
399 	    	        actionTypeCode, 
400 	    	        priority, roleResponsibilityRecipient,
401 	    	        "", // description 
402 	    	        KewApiConstants.MACHINE_GENERATED_RESPONSIBILITY_ID,
403 	    	        forceAction, 
404 	    	        approvePolicy, 
405 	    	        null, // ruleId
406 	    	        null );// annotation
407 	    	requestGraphs.add(requestGraph);
408 	    	parentAnnotation = new StringBuffer();
409 	    	uniqueChildAnnotations = new HashSet<String>( responsibilities.size() );
410     	}
411     	StringBuffer annotation = new StringBuffer();
412     	for (ResponsibilityAction responsibility : responsibilities) {
413     		if ( LOG.isDebugEnabled() ) {
414     			LOG.debug( "Processing Responsibility for action request: " + responsibility );
415     		}
416         	// KFSMI-2381 - pull information from KIM to populate annotation
417     		annotation.setLength( 0 );
418     		Role role = getRoleService().getRole(responsibility.getRoleId());
419     		annotation.append( role.getNamespaceCode() ).append( ' ' ).append( role.getName() ).append( ' ' );
420     		Map<String, String> qualifier = responsibility.getQualifier();
421     		if ( qualifier != null ) {
422 	    		for ( String key : qualifier.keySet() ) {
423 	        		annotation.append( qualifier.get( key ) ).append( ' ' );
424 	    		}
425     		}
426 			if (responsibility.getPrincipalId() != null) {
427 				roleResponsibilityRecipient.setTarget(new KimPrincipalRecipient(responsibility.getPrincipalId()));
428 			} else if (responsibility.getGroupId() != null) {
429 				roleResponsibilityRecipient.setTarget(new KimGroupRecipient(responsibility.getGroupId()));
430 			} else {
431 				throw new RiceRuntimeException("Failed to identify a group or principal on the given ResponsibilityResolutionInfo:" + responsibility);
432 			}
433 			String annotationStr = annotation.toString();
434 			ActionRequestValue request = createActionRequest(
435 			        responsibility.getActionTypeCode(), 
436 			        responsibility.getPriorityNumber(), roleResponsibilityRecipient,
437 			        responsibility.getParallelRoutingGroupingCode(), // description
438 			        responsibility.getResponsibilityId(), 
439 			        responsibility.isForceAction(), 
440 			        // If not nested in a parent action request, ensure that the request
441 			        // is first approve so delegations of this request do not require 
442 			        // ALL_APPROVE as well
443 			        (responsibilities.size() == 1)?ActionRequestPolicy.FIRST.getCode():approvePolicy, 
444 			        null, // ruleId
445 			        annotationStr);
446 			// if there is only a single request, don't create the nesting structure
447 			if ( responsibilities.size() > 1 ) {
448 				request.setParentActionRequest(requestGraph);
449 				requestGraph.getChildrenRequests().add(request);
450 				if ( !uniqueChildAnnotations.contains(annotationStr) ) {
451 					parentAnnotation.append( annotationStr ).append( " -- " );
452 					uniqueChildAnnotations.add(annotationStr);
453 				}
454 			} else {
455 				requestGraphs.add(request);
456 			}
457             generateKimRoleDelegationRequests(responsibility.getDelegates(), request);
458 
459 	    }
460     	if ( responsibilities.size() > 1 ) {
461 	    	requestGraph.setAnnotation( StringUtils.chomp( parentAnnotation.toString(), " -- " ) );
462     	}
463     }
464 
465     private String generateRoleResponsibilityDelegateAnnotation(DelegateMember member, boolean isPrincipal, boolean isGroup, ActionRequestValue parentRequest) {
466     	StringBuffer annotation = new StringBuffer( "Delegation of: " );
467     	annotation.append( parentRequest.getAnnotation() );
468     	annotation.append( " to " );
469     	if (isPrincipal) {
470     		annotation.append( "principal " );
471     		Principal principal = getIdentityService().getPrincipal(member.getMemberId());
472     		if ( principal != null ) {
473     			annotation.append( principal.getPrincipalName() );
474     		} else {
475     			annotation.append( member.getMemberId() );
476     		}
477     	} else if (isGroup) {
478     		annotation.append( "group " );
479     		Group group = getGroupService().getGroup( member.getMemberId() );
480     		if ( group != null ) {
481     			annotation.append( group.getNamespaceCode() ).append( '/' ).append( group.getName() );
482     		} else {
483     			annotation.append( member.getMemberId() );
484     		}
485     	} else {
486     		annotation.append( "?????? '" );
487 			annotation.append( member.getMemberId() );
488     		annotation.append( "'" );
489     	}
490     	return annotation.toString();
491     }
492 
493     public ActionRequestValue addDelegationRoleRequest(ActionRequestValue parentRequest, String approvePolicy, RoleRecipient role, String responsibilityId, Boolean forceAction, DelegationType delegationType, String description, String ruleId) {
494     	Recipient parentRecipient = parentRequest.getRecipient();
495     	if (parentRecipient instanceof RoleRecipient) {
496     		throw new WorkflowRuntimeException("Cannot delegate on Role Request.  It must be a request to a person or workgroup, although that request may be in a role");
497     	}
498     	if (! relatedToRoot(parentRequest)) {
499     		throw new WorkflowRuntimeException("The parent request is not related to any request managed by this factory");
500     	}
501     	ActionRequestValue delegationRoleRequest = createActionRequest(parentRequest.getActionRequested(), parentRequest.getPriority(), role, description, responsibilityId, forceAction, approvePolicy, ruleId, null);
502     	delegationRoleRequest.setDelegationType(delegationType);
503     	int count = 0;
504     	for (Iterator<Id> iter = role.getResolvedQualifiedRole().getRecipients().iterator(); iter.hasNext(); count++) {
505     		//repeat of createRoleRequest code
506     		Id recipientId = iter.next();
507     		if (recipientId.isEmpty()) {
508 				throw new WorkflowRuntimeException("Failed to resolve id of type " + recipientId.getClass().getName() + " returned from role '" + role.getRoleName() + "'.  Id returned contained a null or empty value.");
509 			}
510 			if (recipientId instanceof UserId) {
511 				role.setTarget(new KimPrincipalRecipient(getIdentityHelperService().getPrincipal((UserId) recipientId)));
512 			} else if (recipientId instanceof GroupId) {
513 			    role.setTarget(new KimGroupRecipient(getIdentityHelperService().getGroup((GroupId) recipientId)));
514 			} else {
515 				throw new WorkflowRuntimeException("Could not process the given type of id: " + recipientId.getClass());
516 			}
517 			ActionRequestValue request = createActionRequest(parentRequest.getActionRequested(), parentRequest.getPriority(), role, description, responsibilityId, forceAction, null, ruleId, null);
518 			request.setDelegationType(delegationType);
519 			//end repeat
520 			request.setParentActionRequest(delegationRoleRequest);
521 			delegationRoleRequest.getChildrenRequests().add(request);
522     	}
523 
524     	//put this mini graph in the larger graph
525     	if (count > 0) {
526     		parentRequest.getChildrenRequests().add(delegationRoleRequest);
527     		delegationRoleRequest.setParentActionRequest(parentRequest);
528     	}
529 
530     	return delegationRoleRequest;
531     }
532 
533     public ActionRequestValue addDelegationRequest(ActionRequestValue parentRequest, Recipient recipient, String responsibilityId, Boolean forceAction, DelegationType delegationType, String annotation, String ruleId) {
534     	if (! relatedToRoot(parentRequest)) {
535     		throw new WorkflowRuntimeException("The parent request is not related to any request managed by this factory");
536     	}
537     	ActionRequestValue delegationRequest = createActionRequest(parentRequest.getActionRequested(), parentRequest.getPriority(), recipient, parentRequest.getResponsibilityDesc(), responsibilityId, forceAction, null, ruleId, annotation);
538     	delegationRequest.setDelegationType(delegationType);
539     	
540         parentRequest.getChildrenRequests().add(delegationRequest); 
541         delegationRequest.setParentActionRequest(parentRequest);
542 
543     	return delegationRequest;
544     }
545 
546     //could probably base behavior off of recipient type
547     public ActionRequestValue addRootActionRequest(String actionRequested, Integer priority, Recipient recipient, String description, String responsibilityId, Boolean forceAction, String approvePolicy, String ruleId) {
548     	ActionRequestValue requestGraph = createActionRequest(actionRequested, priority, recipient, description, responsibilityId, forceAction, approvePolicy, ruleId, null);
549     	requestGraphs.add(requestGraph);
550     	return requestGraph;
551     }
552 
553     /**
554      * Generates an ActionRequest graph for the given KIM Responsibilities.  This graph includes any associated delegations.
555      */
556     public void addKimRoleRequest(String actionRequestedCode, Integer priority, Role role,
557             List<RoleMembership> memberships, String description, String responsibilityId, boolean forceAction,
558             String actionRequestPolicyCode, String requestLabel) {
559     	if (CollectionUtils.isEmpty(memberships)) {
560     		LOG.warn("Didn't create action requests for action request description because no role members were defined for role id " + role.getId());
561     		return;
562     	}
563     	
564         KimRoleRecipient roleRecipient = new KimRoleRecipient(role);
565 
566     	// Creation of a parent graph entry for ????
567     	ActionRequestValue requestGraph = null;
568     	if ( memberships.size() > 1 ) {
569 	    	requestGraph = createActionRequest(
570 	    	        actionRequestedCode,
571 	    	        priority,
572                     roleRecipient,
573 	    	        "", // description
574 	    	        responsibilityId,
575 	    	        forceAction,
576 	    	        actionRequestPolicyCode,
577 	    	        null, // ruleId
578 	    	        null );// annotation
579 	    	requestGraphs.add(requestGraph);
580     	}
581     	for (RoleMembership membership : memberships) {
582     		if ( LOG.isDebugEnabled() ) {
583     			LOG.debug( "Processing RoleMembership for action request: " + membership );
584     		}
585 			if (MemberType.PRINCIPAL.equals(membership.getType())) {
586 				roleRecipient.setTarget(new KimPrincipalRecipient(membership.getMemberId()));
587 			} else if (MemberType.GROUP.equals(membership.getType())) {
588 				roleRecipient.setTarget(new KimGroupRecipient(membership.getMemberId()));
589 			} else {
590 				throw new RiceRuntimeException("Failed to identify a group or principal on the given RoleMembership:" + membership);
591 			}
592 			ActionRequestValue request = createActionRequest(
593 			        actionRequestedCode,
594 			        priority,
595                     roleRecipient,
596                     "", // description
597 			        responsibilityId,
598 			        forceAction,
599 			        // If not nested in a parent action request, ensure that the request
600 			        // is first approve so delegations of this request do not require
601 			        // ALL_APPROVE as well
602 			        (memberships.size() == 1) ? ActionRequestPolicy.FIRST.getCode() : actionRequestPolicyCode,
603 			        null, // ruleId
604 			        null); // annotation
605 			// if there is only a single request, don't create the nesting structure
606 			if ( memberships.size() > 1 ) {
607 				request.setParentActionRequest(requestGraph);
608 				requestGraph.getChildrenRequests().add(request);
609 			} else {
610 				requestGraphs.add(request);
611 			}
612             generateKimRoleDelegationRequests(membership.getDelegates(), request);
613 	    }
614     }
615 
616      private void generateKimRoleDelegationRequests(List<DelegateType> delegates, ActionRequestValue parentRequest) {
617     	for (DelegateType delegate : delegates) {
618             for (DelegateMember member : delegate.getMembers()) {
619     		    Recipient recipient;
620     		    boolean isPrincipal = MemberType.PRINCIPAL.equals(member.getType());
621                 boolean isGroup = MemberType.GROUP.equals(member.getType());
622     		    if (isPrincipal) {
623     			    recipient = new KimPrincipalRecipient(member.getMemberId());
624     		    } else if (isGroup) {
625     			    recipient = new KimGroupRecipient(member.getMemberId());
626     		    } else {
627     			    throw new RiceRuntimeException("Invalid DelegateInfo memberTypeCode encountered, was '" + member.getType() + "'");
628     		    }
629     		    String delegationAnnotation = generateRoleResponsibilityDelegateAnnotation(member, isPrincipal, isGroup, parentRequest);
630     		    addDelegationRequest(parentRequest, recipient, delegate.getDelegationId(), parentRequest.getForceAction(), delegate.getDelegationType(), delegationAnnotation, null);
631     	    }
632         }
633     }
634 
635     //return true if requestGraph (root) is in this requests' parents
636     public boolean relatedToRoot(ActionRequestValue request) {
637     	int i = 0;
638     	while(i < 3) {
639     		if (requestGraphs.contains(request)) {
640     			return true;
641     		} else if (request == null) {
642     			return false;
643     		}
644     		i++;
645     		request = request.getParentActionRequest();
646     	}
647     	return false;
648     }
649 
650 	public List<ActionRequestValue> getRequestGraphs() {
651 		//clean up all the trailing role requests with no children -
652 		requestGraphs.removeAll(cleanUpChildren(requestGraphs));
653 		return requestGraphs;
654 	}
655 
656 	private Collection<ActionRequestValue> cleanUpChildren(Collection<ActionRequestValue> children) {
657 		Collection<ActionRequestValue> requestsToRemove = new ArrayList<ActionRequestValue>();
658         for (ActionRequestValue aChildren : children)
659         {
660 
661             if (aChildren.isRoleRequest())
662             {
663                 if (aChildren.getChildrenRequests().isEmpty())
664                 {
665                     requestsToRemove.add(aChildren);
666                 } else
667                 {
668                     Collection<ActionRequestValue> childRequestsToRemove = cleanUpChildren(aChildren.getChildrenRequests());
669                     aChildren.getChildrenRequests().removeAll(childRequestsToRemove);
670 				}
671 			}                                                                                 
672 		}
673 		return requestsToRemove;
674 	}
675 
676     private String generateNotificationAnnotation(PrincipalContract principal, String notificationRequestCode, String actionTakenCode, ActionRequestValue request) {
677     	String notification = "Action " + CodeTranslator.getActionRequestLabel(notificationRequestCode) + " generated by Workflow because " + principal.getPrincipalName() + " took action "
678 				+ CodeTranslator.getActionTakenLabel(actionTakenCode);
679     	// FIXME: KULRICE-5201 switched rsp_id to a varchar, so the comparison below is no longer valid
680 //    	if (request.getResponsibilityId() != null && request.getResponsibilityId() != 0) {
681     	// TODO: KULRICE-5329 Verify that this code below makes sense and is sufficient
682     	if (request.getResponsibilityId() != null && !KewApiConstants.MACHINE_GENERATED_RESPONSIBILITY_ID.equals(request.getResponsibilityId())) {
683     		notification += " Responsibility " + request.getResponsibilityId();
684     	}
685     	if (request.getRuleBaseValuesId() != null) {
686     		notification += " Rule Id " + request.getRuleBaseValuesId();
687     	}
688     	if (request.getAnnotation() != null && request.getAnnotation().length()!=0){
689     		notification += " " + request.getAnnotation();
690     	}
691     	return notification;
692 	}
693 
694     protected static ActionRequestService getActionRequestService() {
695 		if ( actionRequestService == null ) {
696 			actionRequestService = KEWServiceLocator.getActionRequestService();
697 		}
698 		return actionRequestService;
699     }
700 
701 	/**
702 	 * @return the roleService
703 	 */
704     protected static RoleService getRoleService() {
705 		if ( roleService == null ) {
706 			roleService = KimApiServiceLocator.getRoleService();
707 		}
708 		return roleService;
709 	}
710 
711 	/**
712 	 * @return the identityHelperService
713 	 */
714     protected static IdentityHelperService getIdentityHelperService() {
715 		if ( identityHelperService == null ) {
716 			identityHelperService = KEWServiceLocator.getIdentityHelperService();
717 		}
718 		return identityHelperService;
719 	}
720 
721 	/**
722 	 * @return the identityService
723 	 */
724     protected static IdentityService getIdentityService() {
725 		if ( identityService == null ) {
726 			identityService = KimApiServiceLocator.getIdentityService();
727 		}
728 		return identityService;
729 	}
730 
731     protected static GroupService getGroupService() {
732 		if ( groupService == null ) {
733 			groupService = KimApiServiceLocator.getGroupService();
734 		}
735 		return groupService;
736 	}
737 }