001/**
002 * Copyright 2005-2016 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 */
016package org.kuali.rice.kew.actionrequest;
017
018import org.apache.commons.collections.CollectionUtils;
019import org.apache.commons.lang.StringUtils;
020import org.apache.log4j.Logger;
021import org.kuali.rice.core.api.delegation.DelegationType;
022import org.kuali.rice.core.api.exception.RiceRuntimeException;
023import org.kuali.rice.core.api.membership.MemberType;
024import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
025import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
026import org.kuali.rice.kew.api.WorkflowRuntimeException;
027import org.kuali.rice.kew.api.action.ActionRequestPolicy;
028import org.kuali.rice.kew.api.action.ActionRequestStatus;
029import org.kuali.rice.kew.api.action.RecipientType;
030import org.kuali.rice.kew.api.identity.Id;
031import org.kuali.rice.kew.api.user.UserId;
032import org.kuali.rice.kew.api.util.CodeTranslator;
033import org.kuali.rice.kew.engine.RouteContext;
034import org.kuali.rice.kew.engine.node.RouteNodeInstance;
035import org.kuali.rice.kew.identity.service.IdentityHelperService;
036import org.kuali.rice.kew.role.KimRoleRecipient;
037import org.kuali.rice.kew.role.KimRoleResponsibilityRecipient;
038import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
039import org.kuali.rice.kew.rule.ResolvedQualifiedRole;
040import org.kuali.rice.kew.service.KEWServiceLocator;
041import org.kuali.rice.kew.user.RoleRecipient;
042import org.kuali.rice.kew.api.KewApiConstants;
043import org.kuali.rice.kew.util.Utilities;
044import org.kuali.rice.kew.workgroup.GroupId;
045import org.kuali.rice.kim.api.common.delegate.DelegateMember;
046import org.kuali.rice.kim.api.common.delegate.DelegateType;
047import org.kuali.rice.kim.api.group.Group;
048import org.kuali.rice.kim.api.group.GroupService;
049import org.kuali.rice.kim.api.identity.IdentityService;
050import org.kuali.rice.kim.api.identity.principal.Principal;
051import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
052import org.kuali.rice.kim.api.responsibility.ResponsibilityAction;
053import org.kuali.rice.kim.api.role.Role;
054import org.kuali.rice.kim.api.role.RoleMembership;
055import org.kuali.rice.kim.api.role.RoleService;
056import org.kuali.rice.kim.api.services.KimApiServiceLocator;
057import org.kuali.rice.krad.util.KRADConstants;
058import org.kuali.rice.krad.util.ObjectUtils;
059
060import java.sql.Timestamp;
061import java.util.ArrayList;
062import java.util.Collection;
063import java.util.HashSet;
064import java.util.Iterator;
065import java.util.List;
066import java.util.Map;
067import 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 */
075public 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    /**
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}