View Javadoc
1   /**
2    * Copyright 2005-2015 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().getGroupByNamespaceCodeAndName(
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     /**
188      * Generates a notification request for each action request specified, filtering out the specified principal
189      * and delegator, and exclusion workgroup members from notification list
190      * @param parentRequest if non-null, attaches generated notification requests to this parent action request
191      * @param requests list of ActionRequestValues for which to generate corresponding notification requests
192      * @param principal principal to exclude from notifications
193      * @param delegator delegator to exclude from notifications
194      * @param notificationRequestCode the actionrequest code of generated notifications
195      * @param actionTakenCode the actiontaken code to display as the cause of the notification generation
196      * @param notifyExclusionWorkgroup workgroup whose members should not be sent notifications
197      * @return a list of generated notification requests
198      */
199     private List<ActionRequestValue> generateNotifications(ActionRequestValue parentRequest,
200             List requests, PrincipalContract principal, Recipient delegator, String notificationRequestCode,
201             String actionTakenCode, Group notifyExclusionWorkgroup)
202     {
203         List<ActionRequestValue> notificationRequests = new ArrayList<ActionRequestValue>();
204         for (Iterator iter = requests.iterator(); iter.hasNext();) 
205         {
206             ActionRequestValue actionRequest = (ActionRequestValue) iter.next();
207             if (!(actionRequest.isRecipientRoutedRequest(principal.getPrincipalId()) || actionRequest.isRecipientRoutedRequest(delegator)))
208             {
209                 // skip user requests to system users
210                 if ((notifyExclusionWorkgroup != null) &&
211                         (isRecipientInGroup(notifyExclusionWorkgroup, actionRequest.getRecipient())))
212                 {
213                     continue;
214                 }
215                 ActionRequestValue notificationRequest = createNotificationRequest(actionRequest, principal, notificationRequestCode, actionTakenCode);
216                 notificationRequests.add(notificationRequest);
217                 if (parentRequest != null)
218                 {
219                     notificationRequest.setParentActionRequest(parentRequest);
220                     parentRequest.getChildrenRequests().add(notificationRequest);
221                 }
222                 notificationRequests.addAll(generateNotifications(notificationRequest, actionRequest.getChildrenRequests(), principal, delegator, notificationRequestCode, actionTakenCode, notifyExclusionWorkgroup));
223             }
224         }
225         return notificationRequests;
226     }
227 
228     private boolean isRecipientInGroup(Group group, Recipient recipient)
229     {
230         boolean isMember = false;
231 
232         if(recipient instanceof KimPrincipalRecipient)
233         {
234             String principalId = ((KimPrincipalRecipient) recipient).getPrincipalId();
235             String groupId = group.getId();
236             isMember = getGroupService().isMemberOfGroup(principalId, groupId);
237         }
238         else if (recipient instanceof KimGroupRecipient)
239         {
240             String kimRecipientId = ((KimGroupRecipient) recipient).getGroup().getId();
241             isMember = getGroupService().isGroupMemberOfGroup(kimRecipientId, group.getId() );
242         }
243         return isMember;
244     }
245 
246     private ActionRequestValue createNotificationRequest(ActionRequestValue actionRequest, PrincipalContract reasonPrincipal, String notificationRequestCode, String actionTakenCode) {
247 
248     	String annotation = generateNotificationAnnotation(reasonPrincipal, notificationRequestCode, actionTakenCode, actionRequest);
249         ActionRequestValue request = createActionRequest(notificationRequestCode, actionRequest.getPriority(), actionRequest.getRecipient(), actionRequest.getResponsibilityDesc(), KewApiConstants.MACHINE_GENERATED_RESPONSIBILITY_ID, Boolean.TRUE, annotation);
250 
251         request.setDocVersion(actionRequest.getDocVersion());
252         request.setApprovePolicy(actionRequest.getApprovePolicy());
253         request.setRoleName(actionRequest.getRoleName());
254         request.setQualifiedRoleName(actionRequest.getQualifiedRoleName());
255         request.setQualifiedRoleNameLabel(actionRequest.getQualifiedRoleNameLabel());
256         request.setDelegationType(actionRequest.getDelegationType());
257         return request;
258     }
259 
260     private void setDefaultProperties(ActionRequestValue actionRequest) {
261     	if (actionRequest.getApprovePolicy() == null) {
262     		actionRequest.setApprovePolicy(ActionRequestPolicy.FIRST.getCode());
263     	}
264         actionRequest.setCreateDate(new Timestamp(System.currentTimeMillis()));
265         actionRequest.setCurrentIndicator(Boolean.TRUE);
266         if (actionRequest.getForceAction() == null) {
267         	actionRequest.setForceAction(Boolean.FALSE);
268         }
269         if (routeNode != null) {
270         	actionRequest.setNodeInstance(routeNode);
271         }
272         actionRequest.setJrfVerNbr(new Integer(0));
273         actionRequest.setStatus(ActionRequestStatus.INITIALIZED.getCode());
274         actionRequest.setRouteHeader(document);
275         actionRequest.setDocumentId(document.getDocumentId());
276     }
277 
278     private static void resolveRecipient(ActionRequestValue actionRequest, Recipient recipient) {
279     	if (recipient instanceof KimPrincipalRecipient) {
280     		actionRequest.setRecipientTypeCd(RecipientType.PRINCIPAL.getCode());
281     		actionRequest.setPrincipalId(((KimPrincipalRecipient)recipient).getPrincipal().getPrincipalId());
282     	}  else if (recipient instanceof KimGroupRecipient) {
283     		KimGroupRecipient kimGroupRecipient = (KimGroupRecipient)recipient;
284     		actionRequest.setRecipientTypeCd(RecipientType.GROUP.getCode());
285     		actionRequest.setGroupId(kimGroupRecipient.getGroup().getId());
286     	} else if (recipient instanceof RoleRecipient){
287     		RoleRecipient role = (RoleRecipient)recipient;
288     		actionRequest.setRecipientTypeCd(RecipientType.ROLE.getCode());
289     		actionRequest.setRoleName(role.getRoleName());
290     		actionRequest.setQualifiedRoleName(role.getQualifiedRoleName());
291     		ResolvedQualifiedRole qualifiedRole = role.getResolvedQualifiedRole();
292     		if (qualifiedRole != null) {
293     			actionRequest.setAnnotation(qualifiedRole.getAnnotation() == null ? "" : qualifiedRole.getAnnotation());
294     			actionRequest.setQualifiedRoleNameLabel(qualifiedRole.getQualifiedRoleLabel());
295     		}
296     		Recipient targetRecipient = role.getTarget();
297     		if (role.getTarget() != null) {
298     			if (targetRecipient instanceof RoleRecipient) {
299     				throw new WorkflowRuntimeException("Role Cannot Target a role problem activating request for document " + actionRequest.getDocumentId());
300     			}
301     			resolveRecipient(actionRequest, role.getTarget());
302     		}
303     	} else if (recipient instanceof KimRoleResponsibilityRecipient) {
304     		KimRoleResponsibilityRecipient roleResponsibilityRecipient = (KimRoleResponsibilityRecipient)recipient;
305     		actionRequest.setRecipientTypeCd(RecipientType.ROLE.getCode());
306     		actionRequest.setRoleName(roleResponsibilityRecipient.getResponsibilities().get(0).getRoleId());
307     		actionRequest.setQualifiedRoleName(
308                     roleResponsibilityRecipient.getResponsibilities().get(0).getResponsibilityName());
309     		// what about qualified role name label?
310 //    		actionRequest.setAnnotation(roleRecipient.getResponsibilities().get(0).getResponsibilityName());
311     		Recipient targetRecipient = roleResponsibilityRecipient.getTarget();
312     		if (targetRecipient != null) {
313     			if (targetRecipient instanceof RoleRecipient) {
314     				throw new WorkflowRuntimeException("Role Cannot Target a role problem activating request for document " + actionRequest.getDocumentId());
315     			}
316     			resolveRecipient(actionRequest, roleResponsibilityRecipient.getTarget());
317     		}
318     	} else if (recipient instanceof KimRoleRecipient) {
319             KimRoleRecipient roleRecipient = (KimRoleRecipient)recipient;
320             actionRequest.setRecipientTypeCd(RecipientType.ROLE.getCode());
321             Role role = roleRecipient.getRole();
322             actionRequest.setRoleName(role.getId());
323             actionRequest.setQualifiedRoleNameLabel(role.getName());
324             Recipient targetRecipient = roleRecipient.getTarget();
325     		if (targetRecipient != null) {
326     			if (targetRecipient instanceof RoleRecipient) {
327     				throw new WorkflowRuntimeException("Role Cannot Target a role problem activating request for document " + actionRequest.getDocumentId());
328     			}
329     			resolveRecipient(actionRequest, targetRecipient);
330     		}
331         }
332     }
333 
334     /**
335      * Creates a root Role Request
336      * @param role
337      * @param actionRequested
338      * @param approvePolicy
339      * @param priority
340      * @param responsibilityId
341      * @param forceAction
342      * @param description
343      * @param ruleId
344      * @return the created root role request
345      */
346     public ActionRequestValue addRoleRequest(RoleRecipient role, String actionRequested, String approvePolicy, Integer priority, String responsibilityId, Boolean forceAction, String description, String ruleId) {
347 
348     	ActionRequestValue requestGraph = createActionRequest(actionRequested, priority, role, description, responsibilityId, forceAction, approvePolicy, ruleId, null);
349     	if (role != null && role.getResolvedQualifiedRole() != null && role.getResolvedQualifiedRole().getRecipients() != null) {
350     	    int legitimateTargets = 0;
351             for (Iterator<Id> iter = role.getResolvedQualifiedRole().getRecipients().iterator(); iter.hasNext();) {
352                 Id recipientId = (Id) iter.next();
353                 if (recipientId.isEmpty())
354                 {
355                     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.");
356                 }
357                 if (recipientId instanceof UserId)
358                 {
359                     Principal principal = getIdentityHelperService().getPrincipal((UserId) recipientId);
360                     if(ObjectUtils.isNotNull(principal)) {
361                         role.setTarget(new KimPrincipalRecipient(principal));
362                     }
363                 } else if (recipientId instanceof GroupId)
364                 {
365                     role.setTarget(new KimGroupRecipient(getIdentityHelperService().getGroup((GroupId) recipientId)));
366                 } else
367                 {
368                     throw new WorkflowRuntimeException("Could not process the given type of id: " + recipientId.getClass());
369                 }
370                 if (role.getTarget() != null)
371                 {
372                     legitimateTargets++;
373                     ActionRequestValue request = createActionRequest(actionRequested, priority, role, description, responsibilityId, forceAction, null, ruleId, null);
374                     request.setParentActionRequest(requestGraph);
375                     requestGraph.getChildrenRequests().add(request);
376                 }
377             }
378     	if (legitimateTargets == 0) {
379             LOG.warn("Role did not yield any legitimate recipients");
380         }
381     	} else {
382     		LOG.warn("Didn't create action requests for action request description '" + description + "' because of null role or null part of role object graph.");
383     	}
384     	requestGraphs.add(requestGraph);
385     	return requestGraph;
386     }
387 
388     /**
389      * Generates an ActionRequest graph for the given KIM Responsibilities.  This graph includes any associated delegations.
390      * @param responsibilities
391      * @param approvePolicy
392      */
393     public void addRoleResponsibilityRequest(List<ResponsibilityAction> responsibilities, String approvePolicy) {
394     	if (responsibilities == null || responsibilities.isEmpty()) {
395     		LOG.warn("Didn't create action requests for action request description because no responsibilities were defined.");
396     		return;
397     	}
398     	// it's assumed the that all in the list have the same action type code, priority number, etc.
399     	String actionTypeCode = responsibilities.get(0).getActionTypeCode();
400     	Integer priority = responsibilities.get(0).getPriorityNumber();
401     	boolean forceAction = responsibilities.get(0).isForceAction();
402     	KimRoleResponsibilityRecipient roleResponsibilityRecipient = new KimRoleResponsibilityRecipient(responsibilities);
403 
404     	// Creation of a parent graph entry for ????
405     	ActionRequestValue requestGraph = null;
406     	StringBuffer parentAnnotation = null;
407     	// set to allow for suppression of duplicate annotations on the parent action request
408     	Set<String> uniqueChildAnnotations = null;
409     	if ( responsibilities.size() > 1 ) {
410 	    	requestGraph = createActionRequest(
411 	    	        actionTypeCode, 
412 	    	        priority, roleResponsibilityRecipient,
413 	    	        "", // description 
414 	    	        KewApiConstants.MACHINE_GENERATED_RESPONSIBILITY_ID,
415 	    	        forceAction, 
416 	    	        approvePolicy, 
417 	    	        null, // ruleId
418 	    	        null );// annotation
419 	    	requestGraphs.add(requestGraph);
420 	    	parentAnnotation = new StringBuffer();
421 	    	uniqueChildAnnotations = new HashSet<String>( responsibilities.size() );
422     	}
423     	StringBuffer annotation = new StringBuffer();
424     	for (ResponsibilityAction responsibility : responsibilities) {
425     		if ( LOG.isDebugEnabled() ) {
426     			LOG.debug( "Processing Responsibility for action request: " + responsibility );
427     		}
428         	// KFSMI-2381 - pull information from KIM to populate annotation
429     		annotation.setLength( 0 );
430     		Role role = getRoleService().getRole(responsibility.getRoleId());
431     		annotation.append( role.getNamespaceCode() ).append( ' ' ).append( role.getName() ).append( ' ' );
432     		Map<String, String> qualifier = responsibility.getQualifier();
433     		if ( qualifier != null ) {
434 	    		for ( String key : qualifier.keySet() ) {
435 	        		annotation.append( qualifier.get( key ) ).append( ' ' );
436 	    		}
437     		}
438 			if (responsibility.getPrincipalId() != null) {
439 				roleResponsibilityRecipient.setTarget(new KimPrincipalRecipient(responsibility.getPrincipalId()));
440 			} else if (responsibility.getGroupId() != null) {
441 				roleResponsibilityRecipient.setTarget(new KimGroupRecipient(responsibility.getGroupId()));
442 			} else {
443 				throw new RiceRuntimeException("Failed to identify a group or principal on the given ResponsibilityResolutionInfo:" + responsibility);
444 			}
445 			String annotationStr = annotation.toString();
446 			ActionRequestValue request = createActionRequest(
447 			        responsibility.getActionTypeCode(), 
448 			        responsibility.getPriorityNumber(), roleResponsibilityRecipient,
449 			        responsibility.getParallelRoutingGroupingCode(), // description
450 			        responsibility.getResponsibilityId(), 
451 			        responsibility.isForceAction(), 
452 			        // If not nested in a parent action request, ensure that the request
453 			        // is first approve so delegations of this request do not require 
454 			        // ALL_APPROVE as well
455 			        (responsibilities.size() == 1)?ActionRequestPolicy.FIRST.getCode():approvePolicy, 
456 			        null, // ruleId
457 			        annotationStr);
458 			// if there is only a single request, don't create the nesting structure
459 			if ( responsibilities.size() > 1 ) {
460 				request.setParentActionRequest(requestGraph);
461 				requestGraph.getChildrenRequests().add(request);
462 				if ( !uniqueChildAnnotations.contains(annotationStr) ) {
463 					parentAnnotation.append( annotationStr ).append( " -- " );
464 					uniqueChildAnnotations.add(annotationStr);
465 				}
466 			} else {
467 				requestGraphs.add(request);
468 			}
469             generateKimRoleDelegationRequests(responsibility.getDelegates(), request);
470 
471 	    }
472     	if ( responsibilities.size() > 1 ) {
473 	    	requestGraph.setAnnotation( StringUtils.chomp( parentAnnotation.toString(), " -- " ) );
474     	}
475     }
476 
477     private String generateRoleResponsibilityDelegateAnnotation(DelegateMember member, boolean isPrincipal, boolean isGroup, ActionRequestValue parentRequest) {
478     	StringBuffer annotation = new StringBuffer( "Delegation of: " );
479     	annotation.append( parentRequest.getAnnotation() );
480     	annotation.append( " to " );
481     	if (isPrincipal) {
482     		annotation.append( "principal " );
483     		Principal principal = getIdentityService().getPrincipal(member.getMemberId());
484     		if ( principal != null ) {
485     			annotation.append( principal.getPrincipalName() );
486     		} else {
487     			annotation.append( member.getMemberId() );
488     		}
489     	} else if (isGroup) {
490     		annotation.append( "group " );
491     		Group group = getGroupService().getGroup( member.getMemberId() );
492     		if ( group != null ) {
493     			annotation.append( group.getNamespaceCode() ).append( '/' ).append( group.getName() );
494     		} else {
495     			annotation.append( member.getMemberId() );
496     		}
497     	} else {
498     		annotation.append( "?????? '" );
499 			annotation.append( member.getMemberId() );
500     		annotation.append( "'" );
501     	}
502     	return annotation.toString();
503     }
504 
505     public ActionRequestValue addDelegationRoleRequest(ActionRequestValue parentRequest, String approvePolicy, RoleRecipient role, String responsibilityId, Boolean forceAction, DelegationType delegationType, String description, String ruleId) {
506     	Recipient parentRecipient = parentRequest.getRecipient();
507     	if (parentRecipient instanceof RoleRecipient) {
508     		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");
509     	}
510     	if (! relatedToRoot(parentRequest)) {
511     		throw new WorkflowRuntimeException("The parent request is not related to any request managed by this factory");
512     	}
513     	ActionRequestValue delegationRoleRequest = createActionRequest(parentRequest.getActionRequested(), parentRequest.getPriority(), role, description, responsibilityId, forceAction, approvePolicy, ruleId, null);
514     	delegationRoleRequest.setDelegationType(delegationType);
515     	int count = 0;
516     	for (Iterator<Id> iter = role.getResolvedQualifiedRole().getRecipients().iterator(); iter.hasNext(); count++) {
517     		//repeat of createRoleRequest code
518     		Id recipientId = iter.next();
519     		if (recipientId.isEmpty()) {
520 				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.");
521 			}
522 			if (recipientId instanceof UserId) {
523 				role.setTarget(new KimPrincipalRecipient(getIdentityHelperService().getPrincipal((UserId) recipientId)));
524 			} else if (recipientId instanceof GroupId) {
525 			    role.setTarget(new KimGroupRecipient(getIdentityHelperService().getGroup((GroupId) recipientId)));
526 			} else {
527 				throw new WorkflowRuntimeException("Could not process the given type of id: " + recipientId.getClass());
528 			}
529 			ActionRequestValue request = createActionRequest(parentRequest.getActionRequested(), parentRequest.getPriority(), role, description, responsibilityId, forceAction, null, ruleId, null);
530 			request.setDelegationType(delegationType);
531 			//end repeat
532 			request.setParentActionRequest(delegationRoleRequest);
533 			delegationRoleRequest.getChildrenRequests().add(request);
534     	}
535 
536     	//put this mini graph in the larger graph
537     	if (count > 0) {
538     		parentRequest.getChildrenRequests().add(delegationRoleRequest);
539     		delegationRoleRequest.setParentActionRequest(parentRequest);
540     	}
541 
542     	return delegationRoleRequest;
543     }
544 
545     /**
546      * Add a delegation request to the given parent action request.
547      *
548      * @param parentRequest the parent request to add it to
549      * @param recipient the recipient to send the delegation request to
550      * @param responsibilityId
551      * @param forceAction
552      * @param delegationType primary or secondary?
553      * @param actionRequestPolicyCode the action request policy code specifying when the action requests are considered to be completed
554      * @param annotation the annotation to put on the delegation request
555      * @param ruleId
556      * @return the delegation request that was added
557      */
558     public ActionRequestValue addDelegationRequest(ActionRequestValue parentRequest, Recipient recipient, String responsibilityId, Boolean forceAction, DelegationType delegationType, String actionRequestPolicyCode, String annotation, String ruleId) {
559         if (! relatedToRoot(parentRequest)) {
560             throw new WorkflowRuntimeException("The parent request is not related to any request managed by this factory");
561         }
562         ActionRequestValue delegationRequest = createActionRequest(parentRequest.getActionRequested(), parentRequest.getPriority(), recipient, parentRequest.getResponsibilityDesc(), responsibilityId, forceAction, actionRequestPolicyCode, ruleId, annotation);
563         delegationRequest.setDelegationType(delegationType);
564 
565         parentRequest.getChildrenRequests().add(delegationRequest);
566         delegationRequest.setParentActionRequest(parentRequest);
567 
568         return delegationRequest;
569     }
570 
571     /**
572      * Add a delegation request to the given parent action request.
573      *
574      * <p>no action type policy code will be specified.</p>
575      *
576      * @param parentRequest the parent request to add it to
577      * @param recipient the recipient to send the delegation request to
578      * @param responsibilityId
579      * @param forceAction
580      * @param delegationType primary or secondary?
581      * @param annotation the annotation to put on the delegation request
582      * @param ruleId
583      * @return the delegation request that was added
584      */
585     public ActionRequestValue addDelegationRequest(ActionRequestValue parentRequest, Recipient recipient, String responsibilityId, Boolean forceAction, DelegationType delegationType, String annotation, String ruleId) {
586     	return addDelegationRequest(parentRequest, recipient, responsibilityId, forceAction, delegationType, null, annotation, ruleId);
587     }
588 
589     //could probably base behavior off of recipient type
590     public ActionRequestValue addRootActionRequest(String actionRequested, Integer priority, Recipient recipient, String description, String responsibilityId, Boolean forceAction, String approvePolicy, String ruleId) {
591     	ActionRequestValue requestGraph = createActionRequest(actionRequested, priority, recipient, description, responsibilityId, forceAction, approvePolicy, ruleId, null);
592     	requestGraphs.add(requestGraph);
593     	return requestGraph;
594     }
595 
596     /**
597      * Generates an ActionRequest graph for the given KIM Responsibilities.  This graph includes any associated delegations.
598      *
599      * @param actionRequestedCode the type of action requested
600      * @param priority
601      * @param role the role that the members belong to
602      * @param memberships the role members to generate child requests to
603      * @param description
604      * @param responsibilityId
605      * @param forceAction
606      * @param actionRequestPolicyCode the action request policy code specifying when the action requests are considered to be completed
607      * @param requestLabel
608      * @return the request generated for the role members
609      */
610     public ActionRequestValue addKimRoleRequest(String actionRequestedCode, Integer priority, Role role,
611             List<RoleMembership> memberships, String description, String responsibilityId, boolean forceAction,
612             String actionRequestPolicyCode, String requestLabel) {
613         return addKimRoleRequest(actionRequestedCode, priority, role, memberships, description, responsibilityId,
614                 forceAction, actionRequestPolicyCode, requestLabel, /* ignoreKimDelegates = */ false);
615     }
616 
617     /**
618      * Generates an ActionRequest graph for the given KIM Responsibilities.  This graph includes any associated delegations.
619      *
620      * @param actionRequestedCode the type of action requested
621      * @param priority
622      * @param role the role that the members belong to
623      * @param memberships the role members to generate child requests to
624      * @param description
625      * @param responsibilityId
626      * @param forceAction
627      * @param actionRequestPolicyCode the action request policy code specifying when the action requests are considered to be completed
628      * @param requestLabel
629      * @param ignoreKimDelegates should kim delegates be ignored when generating requests
630      * @return the request generated for the role members     */
631     public ActionRequestValue addKimRoleRequest(String actionRequestedCode, Integer priority, Role role,
632             List<RoleMembership> memberships, String description, String responsibilityId, boolean forceAction,
633             String actionRequestPolicyCode, String requestLabel, boolean ignoreKimDelegates) {
634 
635         ActionRequestValue roleMemberRequest = null;
636 
637     	if (CollectionUtils.isEmpty(memberships)) {
638     		LOG.warn("Didn't create action requests for action request description because no role members were defined for role id " + role.getId());
639     		return roleMemberRequest;
640     	}
641 
642         KimRoleRecipient roleRecipient = new KimRoleRecipient(role);
643 
644     	// Creation of a parent graph entry for ????
645     	ActionRequestValue requestGraph = null;
646     	if ( memberships.size() > 1 ) {
647 	    	requestGraph = createActionRequest(
648 	    	        actionRequestedCode,
649 	    	        priority,
650                     roleRecipient,
651 	    	        "", // description
652 	    	        responsibilityId,
653 	    	        forceAction,
654 	    	        actionRequestPolicyCode,
655 	    	        null, // ruleId
656 	    	        null );// annotation
657 	    	requestGraphs.add(requestGraph);
658     	}
659 
660     	for (RoleMembership membership : memberships) {
661     		if ( LOG.isDebugEnabled() ) {
662     			LOG.debug( "Processing RoleMembership for action request: " + membership );
663     		}
664 
665 			if (MemberType.PRINCIPAL.equals(membership.getType())) {
666 				roleRecipient.setTarget(new KimPrincipalRecipient(membership.getMemberId()));
667 			} else if (MemberType.GROUP.equals(membership.getType())) {
668 				roleRecipient.setTarget(new KimGroupRecipient(membership.getMemberId()));
669 			} else {
670 				throw new RiceRuntimeException("Failed to identify a group or principal on the given RoleMembership:" + membership);
671 			}
672 
673 			ActionRequestValue request = createActionRequest(
674 			        actionRequestedCode,
675 			        priority,
676                     roleRecipient,
677                     "", // description
678 			        responsibilityId,
679 			        forceAction,
680 			        // If not nested in a parent action request, ensure that the request
681 			        // is first approve so delegations of this request do not require
682 			        // ALL_APPROVE as well
683 			        (memberships.size() == 1) ? ActionRequestPolicy.FIRST.getCode() : actionRequestPolicyCode,
684 			        null, // ruleId
685 			        null); // annotation
686 
687 			// if there is only a single request, don't create the nesting structure
688 			if ( memberships.size() > 1 ) {
689 				request.setParentActionRequest(requestGraph);
690 				requestGraph.getChildrenRequests().add(request);
691 
692                 if (roleMemberRequest == null) {
693                     roleMemberRequest = requestGraph;
694                 }
695 			} else {
696                 roleMemberRequest = request;
697 				requestGraphs.add(request);
698 			}
699 
700             if (!ignoreKimDelegates) {
701                 generateKimRoleDelegationRequests(membership.getDelegates(), request);
702             }
703 	    }
704 
705         return roleMemberRequest;
706     }
707 
708     /**
709      * Generates a delegate request to a KIM role.
710      *
711      * <p>In other words, the Role is the delegate.  Since delegates in KEW are limited to 1 level, this will ignore
712      * any KIM delegations on the given role.</p>
713      *
714      * @param parentRequest the parent request that the delegate request will be added to
715      * @param actionRequestedCode the type of action requested
716      * @param priority
717      * @param role the role that is being delegated to
718      * @param memberships the role members to generate child requests to
719      * @param description
720      * @param responsibilityId
721      * @param forceAction
722      * @param actionRequestPolicyCode the action request policy code specifying when the action requests are considered to be completed
723      * @param requestLabel
724      * @return the delegate request generated for the role members
725      */
726     public ActionRequestValue addDelegateKimRoleRequest(ActionRequestValue parentRequest, DelegationType delegationType,
727             String actionRequestedCode, Integer priority, Role role, List<RoleMembership> memberships,
728             String description, String responsibilityId, boolean forceAction, String actionRequestPolicyCode,
729             String requestLabel) {
730 
731         // This is a modified version of addKimRoleRequest.  The methods could probably be combined,
732         // but the signature would be even more out of hand and the usage even more confusing.
733 
734         ActionRequestValue delegateRoleRequest = null;
735 
736         if (CollectionUtils.isEmpty(memberships)) {
737             LOG.warn("Didn't create action requests for action request description because no role members were defined for role id " + role.getId());
738             return delegateRoleRequest;
739         }
740 
741         KimRoleRecipient roleRecipient = new KimRoleRecipient(role);
742 
743         // Creation of a parent graph entry for ????
744         ActionRequestValue requestGraph = null;
745         if ( memberships.size() > 1 ) {
746             requestGraph = createActionRequest(
747                     actionRequestedCode,
748                     priority,
749                     roleRecipient,
750                     "", // description
751                     responsibilityId,
752                     forceAction,
753                     actionRequestPolicyCode,
754                     null, // ruleId
755                     null );// annotation
756             requestGraphs.add(requestGraph);
757         }
758 
759         for (RoleMembership membership : memberships) {
760             if ( LOG.isDebugEnabled() ) {
761                 LOG.debug( "Processing RoleMembership for action request: " + membership );
762             }
763 
764             if (MemberType.PRINCIPAL.equals(membership.getType())) {
765                 roleRecipient.setTarget(new KimPrincipalRecipient(membership.getMemberId()));
766             } else if (MemberType.GROUP.equals(membership.getType())) {
767                 roleRecipient.setTarget(new KimGroupRecipient(membership.getMemberId()));
768             } else {
769                 throw new RiceRuntimeException("Failed to identify a group or principal on the given RoleMembership:" + membership);
770             }
771 
772             ActionRequestValue request = createActionRequest(
773                     actionRequestedCode,
774                     priority,
775                     roleRecipient,
776                     "", // description
777                     responsibilityId,
778                     forceAction,
779                     // If not nested in a parent action request, ensure that the request
780                     // is first approve so delegations of this request do not require
781                     // ALL_APPROVE as well
782                     (memberships.size() == 1) ? ActionRequestPolicy.FIRST.getCode() : actionRequestPolicyCode,
783                     null, // ruleId
784                     null); // annotation
785 
786             // if there is only a single request, don't create the nesting structure
787             if ( memberships.size() > 1 ) {
788                 request.setParentActionRequest(requestGraph);
789                 requestGraph.getChildrenRequests().add(request);
790 
791                 if (delegateRoleRequest == null) {
792                     delegateRoleRequest = requestGraph;
793                 }
794             } else {
795                 delegateRoleRequest = request;
796             }
797         }
798 
799         delegateRoleRequest.setDelegationType(delegationType);
800         delegateRoleRequest.setParentActionRequest(parentRequest);
801         parentRequest.getChildrenRequests().add(delegateRoleRequest);
802 
803         return delegateRoleRequest;
804     }
805 
806      private void generateKimRoleDelegationRequests(List<DelegateType> delegates, ActionRequestValue parentRequest) {
807     	for (DelegateType delegate : delegates) {
808             for (DelegateMember member : delegate.getMembers()) {
809     		    Recipient recipient;
810     		    boolean isPrincipal = MemberType.PRINCIPAL.equals(member.getType());
811                 boolean isGroup = MemberType.GROUP.equals(member.getType());
812     		    if (isPrincipal) {
813     			    recipient = new KimPrincipalRecipient(member.getMemberId());
814     		    } else if (isGroup) {
815     			    recipient = new KimGroupRecipient(member.getMemberId());
816     		    } else {
817     			    throw new RiceRuntimeException("Invalid DelegateInfo memberTypeCode encountered, was '" + member.getType() + "'");
818     		    }
819     		    String delegationAnnotation = generateRoleResponsibilityDelegateAnnotation(member, isPrincipal, isGroup, parentRequest);
820     		    addDelegationRequest(parentRequest, recipient, delegate.getDelegationId(), parentRequest.getForceAction(), delegate.getDelegationType(), delegationAnnotation, null);
821     	    }
822         }
823     }
824 
825     //return true if requestGraph (root) is in this requests' parents
826     public boolean relatedToRoot(ActionRequestValue request) {
827     	int i = 0;
828     	while(i < 3) {
829     		if (requestGraphs.contains(request)) {
830     			return true;
831     		} else if (request == null) {
832     			return false;
833     		}
834     		i++;
835     		request = request.getParentActionRequest();
836     	}
837     	return false;
838     }
839 
840 	public List<ActionRequestValue> getRequestGraphs() {
841 		//clean up all the trailing role requests with no children -
842 		requestGraphs.removeAll(cleanUpChildren(requestGraphs));
843 		return requestGraphs;
844 	}
845 
846 	private Collection<ActionRequestValue> cleanUpChildren(Collection<ActionRequestValue> children) {
847 		Collection<ActionRequestValue> requestsToRemove = new ArrayList<ActionRequestValue>();
848         for (ActionRequestValue aChildren : children)
849         {
850 
851             if (aChildren.isRoleRequest())
852             {
853                 if (aChildren.getChildrenRequests().isEmpty())
854                 {
855                     requestsToRemove.add(aChildren);
856                 } else
857                 {
858                     Collection<ActionRequestValue> childRequestsToRemove = cleanUpChildren(aChildren.getChildrenRequests());
859                     aChildren.getChildrenRequests().removeAll(childRequestsToRemove);
860 				}
861 			}                                                                                 
862 		}
863 		return requestsToRemove;
864 	}
865 
866     private String generateNotificationAnnotation(PrincipalContract principal, String notificationRequestCode, String actionTakenCode, ActionRequestValue request) {
867     	String notification = "Action " + CodeTranslator.getActionRequestLabel(notificationRequestCode) + " generated by Workflow because " + principal.getPrincipalName() + " took action "
868 				+ CodeTranslator.getActionTakenLabel(actionTakenCode);
869     	// FIXME: KULRICE-5201 switched rsp_id to a varchar, so the comparison below is no longer valid
870 //    	if (request.getResponsibilityId() != null && request.getResponsibilityId() != 0) {
871     	// TODO: KULRICE-5329 Verify that this code below makes sense and is sufficient
872     	if (request.getResponsibilityId() != null && !KewApiConstants.MACHINE_GENERATED_RESPONSIBILITY_ID.equals(request.getResponsibilityId())) {
873     		notification += " Responsibility " + request.getResponsibilityId();
874     	}
875     	if (request.getRuleBaseValuesId() != null) {
876     		notification += " Rule Id " + request.getRuleBaseValuesId();
877     	}
878     	if (request.getAnnotation() != null && request.getAnnotation().length()!=0){
879     		notification += " " + request.getAnnotation();
880     	}
881     	return notification;
882 	}
883 
884     protected static ActionRequestService getActionRequestService() {
885 		if ( actionRequestService == null ) {
886 			actionRequestService = KEWServiceLocator.getActionRequestService();
887 		}
888 		return actionRequestService;
889     }
890 
891 	/**
892 	 * @return the roleService
893 	 */
894     protected static RoleService getRoleService() {
895 		if ( roleService == null ) {
896 			roleService = KimApiServiceLocator.getRoleService();
897 		}
898 		return roleService;
899 	}
900 
901 	/**
902 	 * @return the identityHelperService
903 	 */
904     protected static IdentityHelperService getIdentityHelperService() {
905 		if ( identityHelperService == null ) {
906 			identityHelperService = KEWServiceLocator.getIdentityHelperService();
907 		}
908 		return identityHelperService;
909 	}
910 
911 	/**
912 	 * @return the identityService
913 	 */
914     protected static IdentityService getIdentityService() {
915 		if ( identityService == null ) {
916 			identityService = KimApiServiceLocator.getIdentityService();
917 		}
918 		return identityService;
919 	}
920 
921     protected static GroupService getGroupService() {
922 		if ( groupService == null ) {
923 			groupService = KimApiServiceLocator.getGroupService();
924 		}
925 		return groupService;
926 	}
927 }