001    /**
002     * Copyright 2005-2013 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.kew.actionrequest;
017    
018    import org.apache.commons.collections.CollectionUtils;
019    import org.apache.commons.lang.StringUtils;
020    import org.apache.log4j.Logger;
021    import org.kuali.rice.core.api.delegation.DelegationType;
022    import org.kuali.rice.core.api.exception.RiceRuntimeException;
023    import org.kuali.rice.core.api.membership.MemberType;
024    import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
025    import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
026    import org.kuali.rice.kew.api.WorkflowRuntimeException;
027    import org.kuali.rice.kew.api.action.ActionRequestPolicy;
028    import org.kuali.rice.kew.api.action.ActionRequestStatus;
029    import org.kuali.rice.kew.api.action.RecipientType;
030    import org.kuali.rice.kew.api.identity.Id;
031    import org.kuali.rice.kew.api.user.UserId;
032    import org.kuali.rice.kew.api.util.CodeTranslator;
033    import org.kuali.rice.kew.engine.RouteContext;
034    import org.kuali.rice.kew.engine.node.RouteNodeInstance;
035    import org.kuali.rice.kew.identity.service.IdentityHelperService;
036    import org.kuali.rice.kew.role.KimRoleRecipient;
037    import org.kuali.rice.kew.role.KimRoleResponsibilityRecipient;
038    import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
039    import org.kuali.rice.kew.rule.ResolvedQualifiedRole;
040    import org.kuali.rice.kew.service.KEWServiceLocator;
041    import org.kuali.rice.kew.user.RoleRecipient;
042    import org.kuali.rice.kew.api.KewApiConstants;
043    import org.kuali.rice.kew.util.Utilities;
044    import org.kuali.rice.kew.workgroup.GroupId;
045    import org.kuali.rice.kim.api.common.delegate.DelegateMember;
046    import org.kuali.rice.kim.api.common.delegate.DelegateType;
047    import org.kuali.rice.kim.api.group.Group;
048    import org.kuali.rice.kim.api.group.GroupService;
049    import org.kuali.rice.kim.api.identity.IdentityService;
050    import org.kuali.rice.kim.api.identity.principal.Principal;
051    import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
052    import org.kuali.rice.kim.api.responsibility.ResponsibilityAction;
053    import org.kuali.rice.kim.api.role.Role;
054    import org.kuali.rice.kim.api.role.RoleMembership;
055    import org.kuali.rice.kim.api.role.RoleService;
056    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
057    import org.kuali.rice.krad.util.KRADConstants;
058    import org.kuali.rice.krad.util.ObjectUtils;
059    
060    import java.sql.Timestamp;
061    import java.util.ArrayList;
062    import java.util.Collection;
063    import java.util.HashSet;
064    import java.util.Iterator;
065    import java.util.List;
066    import java.util.Map;
067    import java.util.Set;
068    
069    
070    /**
071     * A factory to aid in creating the ever-so-gnarly ActionRequestValue object.
072     *
073     * @author Kuali Rice Team (rice.collab@kuali.org)
074     */
075    public class ActionRequestFactory {
076    
077            private static final Logger LOG = Logger.getLogger(ActionRequestFactory.class);
078    
079            private static RoleService roleService;
080            private static IdentityHelperService identityHelperService;
081            private static IdentityService identityService;
082        private static GroupService groupService;
083            private static ActionRequestService actionRequestService;
084            
085            private DocumentRouteHeaderValue document;
086            private RouteNodeInstance routeNode;
087            private List<ActionRequestValue> requestGraphs = new ArrayList<ActionRequestValue>();
088    
089            public ActionRequestFactory() {
090            }
091    
092            public ActionRequestFactory(DocumentRouteHeaderValue document) {
093                    this.document = document;
094            }
095    
096            public ActionRequestFactory(DocumentRouteHeaderValue document, RouteNodeInstance routeNode) {
097                    this.document = document;
098                    this.routeNode = routeNode;
099            }
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        public ActionRequestValue addDelegationRequest(ActionRequestValue parentRequest, Recipient recipient, String responsibilityId, Boolean forceAction, DelegationType delegationType, String annotation, String ruleId) {
546            if (! relatedToRoot(parentRequest)) {
547                    throw new WorkflowRuntimeException("The parent request is not related to any request managed by this factory");
548            }
549            ActionRequestValue delegationRequest = createActionRequest(parentRequest.getActionRequested(), parentRequest.getPriority(), recipient, parentRequest.getResponsibilityDesc(), responsibilityId, forceAction, null, ruleId, annotation);
550            delegationRequest.setDelegationType(delegationType);
551            
552            parentRequest.getChildrenRequests().add(delegationRequest); 
553            delegationRequest.setParentActionRequest(parentRequest);
554    
555            return delegationRequest;
556        }
557    
558        //could probably base behavior off of recipient type
559        public ActionRequestValue addRootActionRequest(String actionRequested, Integer priority, Recipient recipient, String description, String responsibilityId, Boolean forceAction, String approvePolicy, String ruleId) {
560            ActionRequestValue requestGraph = createActionRequest(actionRequested, priority, recipient, description, responsibilityId, forceAction, approvePolicy, ruleId, null);
561            requestGraphs.add(requestGraph);
562            return requestGraph;
563        }
564    
565        /**
566         * Generates an ActionRequest graph for the given KIM Responsibilities.  This graph includes any associated delegations.
567         */
568        public void addKimRoleRequest(String actionRequestedCode, Integer priority, Role role,
569                List<RoleMembership> memberships, String description, String responsibilityId, boolean forceAction,
570                String actionRequestPolicyCode, String requestLabel) {
571            if (CollectionUtils.isEmpty(memberships)) {
572                    LOG.warn("Didn't create action requests for action request description because no role members were defined for role id " + role.getId());
573                    return;
574            }
575            
576            KimRoleRecipient roleRecipient = new KimRoleRecipient(role);
577    
578            // Creation of a parent graph entry for ????
579            ActionRequestValue requestGraph = null;
580            if ( memberships.size() > 1 ) {
581                    requestGraph = createActionRequest(
582                            actionRequestedCode,
583                            priority,
584                        roleRecipient,
585                            "", // description
586                            responsibilityId,
587                            forceAction,
588                            actionRequestPolicyCode,
589                            null, // ruleId
590                            null );// annotation
591                    requestGraphs.add(requestGraph);
592            }
593            for (RoleMembership membership : memberships) {
594                    if ( LOG.isDebugEnabled() ) {
595                            LOG.debug( "Processing RoleMembership for action request: " + membership );
596                    }
597                            if (MemberType.PRINCIPAL.equals(membership.getType())) {
598                                    roleRecipient.setTarget(new KimPrincipalRecipient(membership.getMemberId()));
599                            } else if (MemberType.GROUP.equals(membership.getType())) {
600                                    roleRecipient.setTarget(new KimGroupRecipient(membership.getMemberId()));
601                            } else {
602                                    throw new RiceRuntimeException("Failed to identify a group or principal on the given RoleMembership:" + membership);
603                            }
604                            ActionRequestValue request = createActionRequest(
605                                    actionRequestedCode,
606                                    priority,
607                        roleRecipient,
608                        "", // description
609                                    responsibilityId,
610                                    forceAction,
611                                    // If not nested in a parent action request, ensure that the request
612                                    // is first approve so delegations of this request do not require
613                                    // ALL_APPROVE as well
614                                    (memberships.size() == 1) ? ActionRequestPolicy.FIRST.getCode() : actionRequestPolicyCode,
615                                    null, // ruleId
616                                    null); // annotation
617                            // if there is only a single request, don't create the nesting structure
618                            if ( memberships.size() > 1 ) {
619                                    request.setParentActionRequest(requestGraph);
620                                    requestGraph.getChildrenRequests().add(request);
621                            } else {
622                                    requestGraphs.add(request);
623                            }
624                generateKimRoleDelegationRequests(membership.getDelegates(), request);
625                }
626        }
627    
628         private void generateKimRoleDelegationRequests(List<DelegateType> delegates, ActionRequestValue parentRequest) {
629            for (DelegateType delegate : delegates) {
630                for (DelegateMember member : delegate.getMembers()) {
631                        Recipient recipient;
632                        boolean isPrincipal = MemberType.PRINCIPAL.equals(member.getType());
633                    boolean isGroup = MemberType.GROUP.equals(member.getType());
634                        if (isPrincipal) {
635                                recipient = new KimPrincipalRecipient(member.getMemberId());
636                        } else if (isGroup) {
637                                recipient = new KimGroupRecipient(member.getMemberId());
638                        } else {
639                                throw new RiceRuntimeException("Invalid DelegateInfo memberTypeCode encountered, was '" + member.getType() + "'");
640                        }
641                        String delegationAnnotation = generateRoleResponsibilityDelegateAnnotation(member, isPrincipal, isGroup, parentRequest);
642                        addDelegationRequest(parentRequest, recipient, delegate.getDelegationId(), parentRequest.getForceAction(), delegate.getDelegationType(), delegationAnnotation, null);
643                }
644            }
645        }
646    
647        //return true if requestGraph (root) is in this requests' parents
648        public boolean relatedToRoot(ActionRequestValue request) {
649            int i = 0;
650            while(i < 3) {
651                    if (requestGraphs.contains(request)) {
652                            return true;
653                    } else if (request == null) {
654                            return false;
655                    }
656                    i++;
657                    request = request.getParentActionRequest();
658            }
659            return false;
660        }
661    
662            public List<ActionRequestValue> getRequestGraphs() {
663                    //clean up all the trailing role requests with no children -
664                    requestGraphs.removeAll(cleanUpChildren(requestGraphs));
665                    return requestGraphs;
666            }
667    
668            private Collection<ActionRequestValue> cleanUpChildren(Collection<ActionRequestValue> children) {
669                    Collection<ActionRequestValue> requestsToRemove = new ArrayList<ActionRequestValue>();
670            for (ActionRequestValue aChildren : children)
671            {
672    
673                if (aChildren.isRoleRequest())
674                {
675                    if (aChildren.getChildrenRequests().isEmpty())
676                    {
677                        requestsToRemove.add(aChildren);
678                    } else
679                    {
680                        Collection<ActionRequestValue> childRequestsToRemove = cleanUpChildren(aChildren.getChildrenRequests());
681                        aChildren.getChildrenRequests().removeAll(childRequestsToRemove);
682                                    }
683                            }                                                                                 
684                    }
685                    return requestsToRemove;
686            }
687    
688        private String generateNotificationAnnotation(PrincipalContract principal, String notificationRequestCode, String actionTakenCode, ActionRequestValue request) {
689            String notification = "Action " + CodeTranslator.getActionRequestLabel(notificationRequestCode) + " generated by Workflow because " + principal.getPrincipalName() + " took action "
690                                    + CodeTranslator.getActionTakenLabel(actionTakenCode);
691            // FIXME: KULRICE-5201 switched rsp_id to a varchar, so the comparison below is no longer valid
692    //      if (request.getResponsibilityId() != null && request.getResponsibilityId() != 0) {
693            // TODO: KULRICE-5329 Verify that this code below makes sense and is sufficient
694            if (request.getResponsibilityId() != null && !KewApiConstants.MACHINE_GENERATED_RESPONSIBILITY_ID.equals(request.getResponsibilityId())) {
695                    notification += " Responsibility " + request.getResponsibilityId();
696            }
697            if (request.getRuleBaseValuesId() != null) {
698                    notification += " Rule Id " + request.getRuleBaseValuesId();
699            }
700            if (request.getAnnotation() != null && request.getAnnotation().length()!=0){
701                    notification += " " + request.getAnnotation();
702            }
703            return notification;
704            }
705    
706        protected static ActionRequestService getActionRequestService() {
707                    if ( actionRequestService == null ) {
708                            actionRequestService = KEWServiceLocator.getActionRequestService();
709                    }
710                    return actionRequestService;
711        }
712    
713            /**
714             * @return the roleService
715             */
716        protected static RoleService getRoleService() {
717                    if ( roleService == null ) {
718                            roleService = KimApiServiceLocator.getRoleService();
719                    }
720                    return roleService;
721            }
722    
723            /**
724             * @return the identityHelperService
725             */
726        protected static IdentityHelperService getIdentityHelperService() {
727                    if ( identityHelperService == null ) {
728                            identityHelperService = KEWServiceLocator.getIdentityHelperService();
729                    }
730                    return identityHelperService;
731            }
732    
733            /**
734             * @return the identityService
735             */
736        protected static IdentityService getIdentityService() {
737                    if ( identityService == null ) {
738                            identityService = KimApiServiceLocator.getIdentityService();
739                    }
740                    return identityService;
741            }
742    
743        protected static GroupService getGroupService() {
744                    if ( groupService == null ) {
745                            groupService = KimApiServiceLocator.getGroupService();
746                    }
747                    return groupService;
748            }
749    }