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.impl.peopleflow;
017
018import org.apache.commons.collections.CollectionUtils;
019import org.apache.commons.lang.StringUtils;
020import org.kuali.rice.core.api.CoreConstants;
021import org.kuali.rice.core.api.config.ConfigurationException;
022import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
023import org.kuali.rice.core.api.exception.RiceIllegalStateException;
024import org.kuali.rice.core.api.exception.RiceRuntimeException;
025import org.kuali.rice.core.api.membership.MemberType;
026import org.kuali.rice.core.api.util.VersionHelper;
027import org.kuali.rice.kew.actionrequest.ActionRequestFactory;
028import org.kuali.rice.kew.actionrequest.ActionRequestValue;
029import org.kuali.rice.kew.actionrequest.KimGroupRecipient;
030import org.kuali.rice.kew.actionrequest.KimPrincipalRecipient;
031import org.kuali.rice.kew.actionrequest.Recipient;
032import org.kuali.rice.kew.api.action.ActionRequestPolicy;
033import org.kuali.rice.kew.api.action.ActionRequestType;
034import org.kuali.rice.kew.api.action.RecipientType;
035import org.kuali.rice.kew.api.document.Document;
036import org.kuali.rice.kew.api.document.DocumentContent;
037import org.kuali.rice.kew.api.peopleflow.PeopleFlowDefinition;
038import org.kuali.rice.kew.api.peopleflow.PeopleFlowDelegate;
039import org.kuali.rice.kew.api.peopleflow.PeopleFlowMember;
040import org.kuali.rice.kew.api.repository.type.KewTypeDefinition;
041import org.kuali.rice.kew.api.repository.type.KewTypeRepositoryService;
042import org.kuali.rice.kew.engine.RouteContext;
043import org.kuali.rice.kew.framework.peopleflow.PeopleFlowTypeService;
044import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
045import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValueContent;
046import org.kuali.rice.kim.api.group.Group;
047import org.kuali.rice.kim.api.identity.principal.Principal;
048import org.kuali.rice.kim.api.role.Role;
049import org.kuali.rice.kim.api.role.RoleMembership;
050import org.kuali.rice.kim.api.role.RoleService;
051import org.kuali.rice.kim.api.services.KimApiServiceLocator;
052import org.kuali.rice.ksb.api.KsbApiServiceLocator;
053import org.kuali.rice.ksb.api.bus.Endpoint;
054
055import javax.xml.namespace.QName;
056import java.util.ArrayList;
057import java.util.Collections;
058import java.util.List;
059import java.util.Map;
060
061/**
062 * Reference implementation of the {@code PeopleFlowRequestGenerator} which is responsible for generating Action
063 * Requests from a {@link PeopleFlowDefinition}.
064 *
065 * @author Kuali Rice Team (rice.collab@kuali.org)
066 */
067public class PeopleFlowRequestGeneratorImpl implements PeopleFlowRequestGenerator {
068
069    private KewTypeRepositoryService typeRepositoryService;
070    private RoleService roleService;
071
072    @Override
073    public List<ActionRequestValue> generateRequests(RouteContext routeContext, PeopleFlowDefinition peopleFlow, ActionRequestType actionRequested) {
074        Context context = new Context(routeContext, peopleFlow, actionRequested);
075        for (PeopleFlowMember member : peopleFlow.getMembers()) {
076            generateRequestForMember(context, member);
077        }
078
079        return context.getActionRequestFactory().getRequestGraphs();
080    }
081
082    protected void generateRequestForMember(Context context, PeopleFlowMember member) {
083        if (MemberType.ROLE == member.getMemberType()) {
084            // for role members, delegates are generated within this call
085            generateRequestsForRoleMember(context, member);
086        } else {
087            ActionRequestValue actionRequest = context.getActionRequestFactory().addRootActionRequest(
088                    context.getActionRequested().getCode(), member.getPriority(), toRecipient(member), "",
089                    member.getResponsibilityId(), member.getForceAction(), getActionRequestPolicyCode(member), null);
090
091            if (actionRequest != null) {
092                generateDelegationRequests(context, Collections.singletonList(actionRequest), member);
093            }
094        }
095    }
096
097    /**
098     * generates requests for a PeopleFlow member of type Role.
099     *
100     * <p>Will resolve role qualifiers through the appropriate PeopleFlowTypeService, and if multiple qualifier maps are
101     * generated, it can generate multiple request trees.
102     * parent requests</p>
103     *
104     * @param context the context for request generation
105     * @param member the PeopleFlow member, which is assumed to be of MemberType ROLE
106     * @return a list of root action requests that were generated, or an empty list if no role members were resolved
107     */
108    protected List<ActionRequestValue> generateRequestsForRoleMember(Context context, PeopleFlowMember member) {
109        List<ActionRequestValue> roleMemberRequests = new ArrayList<ActionRequestValue>(); // results
110
111        List<Map<String, String>> roleQualifierMaps = loadRoleQualifiers(context, member.getMemberId());
112
113        // if we got no role qualifier maps, this means we need to generate a single request without any qualifiers.
114        // Use a list with one empty map in it to force adding a role request without qualifiers in the loop below.
115        if (CollectionUtils.isEmpty(roleQualifierMaps)) {
116            roleQualifierMaps = Collections.singletonList((Map<String,String>)Collections.EMPTY_MAP);
117        }
118
119        Role role = getRoleService().getRole(member.getMemberId());
120
121        boolean hasPeopleFlowDelegates = !CollectionUtils.isEmpty(member.getDelegates());
122
123        if (role == null) {
124            throw new IllegalStateException("Failed to locate a role with the given role id of '" +
125                    member.getMemberId() + "'");
126        }
127
128        for (Map<String, String> roleQualifiers : roleQualifierMaps) {
129            ActionRequestValue request = addKimRoleRequest(context, member, role, roleQualifiers,
130                    hasPeopleFlowDelegates);
131
132            if (request != null) {
133                roleMemberRequests.add(request);
134                generateDelegationRequestsForRoleMember(context, request, member, roleQualifiers);
135            }
136        }
137
138        return roleMemberRequests;
139    }
140
141    /**
142     * Uses the ActionRequestFactory to build the request tree for the role members.
143     *
144     * <p>The role members themselves are derived here using the given qualifiers.</p>
145     *
146     * @param context the context for request generation
147     * @param member the PeopleFlow member
148     * @param role the role specified within the member
149     * @param roleQualifiers the qualifiers to use for role member selection
150     * @param ignoreKimDelegates should KIM delegates be ignored when generating requests?
151     * @return the root request of the generated action request tree, or null if no members are found
152     */
153    private ActionRequestValue addKimRoleRequest(Context context, PeopleFlowMember member, Role role,
154            Map<String, String> roleQualifiers, boolean ignoreKimDelegates) {
155
156        ActionRequestValue roleMemberRequest = null;
157
158        List<RoleMembership> memberships = getRoleService().getRoleMembers(Collections.singletonList(
159                member.getMemberId()), roleQualifiers);
160
161        String actionRequestPolicyCode = getActionRequestPolicyCode(member);
162
163        if (!CollectionUtils.isEmpty(memberships)) {
164            roleMemberRequest = context.getActionRequestFactory().addKimRoleRequest(
165                    context.getActionRequested().getCode(), member.getPriority(), role, memberships, null,
166                    member.getResponsibilityId(), member.getForceAction(), actionRequestPolicyCode, null, ignoreKimDelegates);
167        }
168
169        return roleMemberRequest;
170    }
171
172    /**
173     * Generates any needed requests for {@link PeopleFlowDelegate}s on the given non-Role member.
174     *
175     * <p>If there are no delegates, or if no requests were generated for the member, then this will be a no-op.</p>
176     *
177     * @param context the context for request generation
178     * @param memberRequests any action requests that were generated for the given member
179     * @param member the PeopleFlow member
180     * @throws org.kuali.rice.core.api.exception.RiceRuntimeException if member has memberType of ROLE.
181     */
182    private void generateDelegationRequests(Context context, List<ActionRequestValue> memberRequests,
183            PeopleFlowMember member) {
184
185        if (MemberType.ROLE == member.getMemberType()) {
186            throw new RiceRuntimeException("delegation for Role members");
187        }
188
189        if (CollectionUtils.isEmpty(member.getDelegates()) || CollectionUtils.isEmpty(memberRequests)) {
190            return;
191        }
192
193        for (PeopleFlowDelegate delegate : member.getDelegates()) {
194            for (ActionRequestValue memberRequest : memberRequests) {
195                if (MemberType.ROLE != delegate.getMemberType()) {
196                    generateDelegationToNonRoleRequest(context, memberRequest, member, delegate);
197                } else {
198                    generateDelegationToRoleRequests(context, memberRequest, member, delegate);
199                }
200            }
201        }
202    }
203
204    /**
205     * Uses the ActionRequestFactory to add the delegate request to the given parent request.
206     *
207     * <p>Only handles non-role delegates.  If a delegate of type role is passed, a RiceIllegalStateException will be
208     * thrown.</p>
209     *
210     * @param context the context for request generation
211     * @param memberRequest an action request that was generated for the given member
212     * @param member the PeopleFlow member
213     * @param delegate the delegate to generate a request to
214     */
215    private void generateDelegationToNonRoleRequest(Context context, ActionRequestValue memberRequest,
216            PeopleFlowMember member, PeopleFlowDelegate delegate) {
217
218        Recipient recipient;
219
220        if (MemberType.PRINCIPAL == delegate.getMemberType()) {
221            recipient = new KimPrincipalRecipient(delegate.getMemberId());
222        } else if (MemberType.GROUP == delegate.getMemberType()) {
223            recipient = new KimGroupRecipient(delegate.getMemberId());
224        } else {
225            throw new RiceIllegalStateException("MemberType unknown: " + delegate.getMemberType());
226        }
227
228        String delegationAnnotation = generateDelegationAnnotation(memberRequest, member, delegate);
229
230        context.getActionRequestFactory().addDelegationRequest(memberRequest, recipient,
231                delegate.getResponsibilityId(), member.getForceAction(),
232                delegate.getDelegationType(), delegationAnnotation, null);
233    }
234
235    /**
236     * Generates any needed requests for the given {@link PeopleFlowDelegate}.
237     *
238     * <p>It is assumed that the given member is a Role.</p>
239     *
240     * @param context the context for request generation
241     * @param parentRequest an action request that was generated for the given member
242     * @param member the PeopleFlow member, which should contain the given delegate
243     * @param roleQualifiers member's qualifiers
244     */
245    protected void generateDelegationRequestsForRoleMember(Context context, ActionRequestValue parentRequest,
246            PeopleFlowMember member, Map<String, String> roleQualifiers) {
247
248        if (CollectionUtils.isEmpty(member.getDelegates())) {
249            return;
250        }
251
252        // To apply delegates to a role request, you have to dig out the non-role child requests and generate the
253        // delegates on each of them.  Otherwise you get a malformed action request tree that doesn't function correctly.
254        List<ActionRequestValue> nonRoleRequests = findNonRoleRequests(Collections.singletonList(parentRequest));
255
256        if (CollectionUtils.isEmpty(nonRoleRequests)) {
257            return;
258        }
259
260        for (ActionRequestValue nonRoleRequest : nonRoleRequests) {
261            generateDelegationRequestsForRoleMemberRequest(context, nonRoleRequest, member, roleQualifiers);
262        }
263    }
264
265    /**
266     * Generates any needed requests for the given {@link PeopleFlowDelegate}.
267     *
268     * <p>It is assumed that the given member is a Role.</p>
269     *
270     * @param context the context for request generation
271     * @param roleRequestChild a <em>non-role</em> action request that was generated for the given Role member
272     * @param member the PeopleFlow member, which should contain the given delegate
273     * @param roleQualifier member's qualifier
274     */
275    private void generateDelegationRequestsForRoleMemberRequest(Context context, ActionRequestValue roleRequestChild,
276            PeopleFlowMember member, Map<String, String> roleQualifier) {
277
278        for (PeopleFlowDelegate delegate : member.getDelegates()) {
279            // we're handling all delegation requests for Role members here, so we need to handle all delegate member types
280            if (MemberType.ROLE.equals(delegate.getMemberType())) {
281                Role delegateRole = getRoleService().getRole(delegate.getMemberId());
282
283                if (delegateRole != null) {
284                    addKimRoleDelegateRequest(context, roleRequestChild, member, delegate, delegateRole,
285                            roleQualifier);
286                }
287            } else {
288                generateDelegationToNonRoleRequest(context, roleRequestChild, member, delegate);
289            }
290        }
291    }
292
293    /**
294     * Builds the String that will be used for the annotation on the delegate requests
295     *
296     * @param parentRequest an action request that was generated for the given member
297     * @param member the PeopleFlow member
298     * @param delegate the delegate
299     * @return the annotation string
300     */
301    private String generateDelegationAnnotation(ActionRequestValue parentRequest, PeopleFlowMember member,
302            PeopleFlowDelegate delegate) {
303
304        StringBuffer annotation = new StringBuffer( "Delegation of: " );
305        annotation.append( parentRequest.getAnnotation() );
306        annotation.append( " to " );
307
308        if (delegate.getMemberType() == MemberType.PRINCIPAL) {
309            annotation.append( "principal " );
310            Principal principal = KimApiServiceLocator.getIdentityService().getPrincipal(delegate.getMemberId());
311
312            if ( principal != null ) {
313                annotation.append( principal.getPrincipalName() );
314            } else {
315                annotation.append( member.getMemberId() );
316            }
317        } else if (delegate.getMemberType() == MemberType.GROUP) {
318            annotation.append( "group " );
319            Group group = KimApiServiceLocator.getGroupService().getGroup(delegate.getMemberId());
320
321            if ( group != null ) {
322                annotation.append( group.getNamespaceCode() ).append( '/' ).append( group.getName() );
323            } else {
324                annotation.append( member.getMemberId() );
325            }
326        } else {
327            annotation.append( "?????? '" );
328            annotation.append( member.getMemberId() );
329            annotation.append( "'" );
330        }
331
332        return annotation.toString();
333    }
334
335    /**
336     * Generates any needed requests for the given {@link PeopleFlowDelegate}.
337     *
338     * <p>It is assumed that the given member has the given delegate configured.</p>
339     *
340     * @param context the context for request generation
341     * @param parentRequest an action request that was generated for the given member
342     * @param member the PeopleFlow member, which should contain the given delegate
343     * @param delegate the delegate, assumed to be of MemberType ROLE, to generate a request to
344     */
345    protected void generateDelegationToRoleRequests(Context context,
346            ActionRequestValue parentRequest, PeopleFlowMember member, PeopleFlowDelegate delegate) {
347
348        List<Map<String, String>> roleQualifierList = loadRoleQualifiers(context, delegate.getMemberId());
349        Role role = getRoleService().getRole(delegate.getMemberId());
350
351        if (role == null) {
352            throw new IllegalStateException("Failed to locate a role with the given role id of '" +
353                    delegate.getMemberId() + "'");
354        }
355
356        if (CollectionUtils.isEmpty(roleQualifierList)) {
357            addKimRoleDelegateRequest(context, parentRequest, member, delegate, role,
358                    Collections.<String, String>emptyMap());
359        } else {
360            for (Map<String, String> roleQualifiers : roleQualifierList) {
361                addKimRoleDelegateRequest(context, parentRequest, member, delegate, role, roleQualifiers);
362            }
363        }
364    }
365
366    /**
367     * Helper method uses the ActionRequestFactory to add to the parent request the delegation request(s) to a role.
368     *
369     * <p>The role members themselves are derived here using the given qualifiers.</p>
370     *
371     * @param context the context for request generation
372     * @param parentRequest an action request that was generated for the given member
373     * @param member the PeopleFlow member
374     * @param delegate the delegate to generate a request to
375     * @param role the role specified within the delegate
376     * @param roleQualifiers the qualifiers to use for role member selection
377     */
378    private void addKimRoleDelegateRequest(Context context, ActionRequestValue parentRequest,
379            PeopleFlowMember member, PeopleFlowDelegate delegate, Role role, Map<String, String> roleQualifiers) {
380
381        // sanity check
382        if (delegate.getMemberType() != MemberType.ROLE) {
383            throw new RiceIllegalArgumentException("delegate's member type must be ROLE");
384        } else if (!delegate.getMemberId().equals(role.getId())) {
385            throw new RiceIllegalArgumentException("delegate's member id must match the given role's id");
386        }
387
388        String actionRequestPolicyCode = getDelegateActionRequestPolicyCode(member, delegate);
389
390        List<RoleMembership> memberships = getRoleService().getRoleMembers(Collections.singletonList(
391                delegate.getMemberId()), roleQualifiers);
392
393        if (!CollectionUtils.isEmpty(memberships)) {
394            context.getActionRequestFactory().addDelegateKimRoleRequest(parentRequest,
395                    delegate.getDelegationType(), context.getActionRequested().getCode(), member.getPriority(), role,
396                    memberships, null, delegate.getResponsibilityId(), member.getForceAction(), actionRequestPolicyCode, null);
397        }
398    }
399
400    /**
401     * Uses the appropriate {@link PeopleFlowTypeService} to get the role qualifier maps for the given document and
402     * role.
403     *
404     * <p>Note that the PeopleFlowTypeService is selected based on the type id of the PeopleFlow.</p>
405     *
406     * @param context the context for request generation
407     * @param roleId the ID of the role for whom qualifiers are being loaded
408     * @return the qualifier maps, or an empty list if there are none
409     */
410    protected List<Map<String, String>> loadRoleQualifiers(Context context, String roleId) {
411        PeopleFlowTypeService peopleFlowTypeService = context.getPeopleFlowTypeService();
412        List<Map<String, String>> roleQualifierList = new ArrayList<Map<String, String>>();
413
414        if (peopleFlowTypeService != null) {
415            Document document = DocumentRouteHeaderValue.to(context.getRouteContext().getDocument());
416            DocumentRouteHeaderValueContent content = new DocumentRouteHeaderValueContent(document.getDocumentId());
417            content.setDocumentContent(context.getRouteContext().getDocumentContent().getDocContent());
418            DocumentContent documentContent = DocumentRouteHeaderValueContent.to(content);
419
420            Map<String, String> roleQualifiers = peopleFlowTypeService.resolveRoleQualifiers(
421                    context.getPeopleFlow().getTypeId(), roleId, document, documentContent
422            );
423
424            if (roleQualifiers != null) {
425                roleQualifierList.add(roleQualifiers);
426            }
427
428            boolean versionOk = VersionHelper.compareVersion(context.getPeopleFlowTypeServiceVersion(), CoreConstants.Versions.VERSION_2_3_0) != -1;
429            if(versionOk) {
430                List<Map<String, String>> multipleRoleQualifiers = peopleFlowTypeService.resolveMultipleRoleQualifiers(
431                        context.getPeopleFlow().getTypeId(), roleId, document, documentContent);
432
433                if (multipleRoleQualifiers != null) {
434                    roleQualifierList.addAll(multipleRoleQualifiers);
435                }
436            }
437
438        }
439
440        return roleQualifierList;
441    }
442
443    /**
444     * Gets the action request policy code for the given delegate.
445     *
446     * <p>the delegate is considered first, and the member is used as a fallback.  May return null.</p>
447     *
448     * @param member the PeopleFlow member
449     * @param delegate the delegate
450     * @return the action request policy code, or null if none is found
451     */
452    private String getDelegateActionRequestPolicyCode(PeopleFlowMember member, PeopleFlowDelegate delegate) {
453        ActionRequestPolicy actionRequestPolicy = delegate.getActionRequestPolicy();
454
455        return (actionRequestPolicy != null) ? actionRequestPolicy.getCode() : getActionRequestPolicyCode(member);
456    }
457
458    /**
459     * Gets the action request policy code for the given member.
460     *
461     * @param member the PeopleFlow member
462     * @return the action request policy code, or null if none is found
463     */
464    private String getActionRequestPolicyCode(PeopleFlowMember member) {
465        ActionRequestPolicy actionRequestPolicy = member.getActionRequestPolicy();
466
467        return (actionRequestPolicy != null) ? actionRequestPolicy.getCode() : null;
468    }
469
470    private Recipient toRecipient(PeopleFlowMember member) {
471        Recipient recipient;
472        if (MemberType.PRINCIPAL == member.getMemberType()) {
473            recipient = new KimPrincipalRecipient(member.getMemberId());
474        } else if (MemberType.GROUP == member.getMemberType()) {
475            recipient = new KimGroupRecipient(member.getMemberId());
476        } else {
477            throw new IllegalStateException("encountered a member type which I did not understand: " +
478                    member.getMemberType());
479        }
480        return recipient;
481    }
482
483    private Recipient toRecipient(PeopleFlowDelegate delegate) {
484        Recipient recipient;
485        if (MemberType.PRINCIPAL == delegate.getMemberType()) {
486            recipient = new KimPrincipalRecipient(delegate.getMemberId());
487        } else if (MemberType.GROUP == delegate.getMemberType()) {
488            recipient = new KimGroupRecipient(delegate.getMemberId());
489        } else {
490            throw new IllegalStateException("encountered a delegate member type which I did not understand: " +
491                    delegate.getMemberType());
492        }
493        return recipient;
494    }
495
496    public KewTypeRepositoryService getTypeRepositoryService() {
497        return typeRepositoryService;
498    }
499
500    public void setTypeRepositoryService(KewTypeRepositoryService typeRepositoryService) {
501        this.typeRepositoryService = typeRepositoryService;
502    }
503
504    public RoleService getRoleService() {
505        return roleService;
506    }
507
508    public void setRoleService(RoleService roleService) {
509        this.roleService = roleService;
510    }
511
512    /**
513     * Recursively find all non-delegate Group and Principal requests from all of the requests in the given list.
514     *
515     * @param actionRequestValues the list of {@link ActionRequestValue}s to search
516     * @return a list of the non-delegate Group and Principal requests found
517     */
518    private List<ActionRequestValue> findNonRoleRequests(List<ActionRequestValue> actionRequestValues) {
519        List<ActionRequestValue> nonRoleRequests = new ArrayList<ActionRequestValue>();
520
521        return findNonRoleRequests(actionRequestValues, nonRoleRequests);
522    }
523
524    // Recursion helper method
525    private List<ActionRequestValue> findNonRoleRequests(List<ActionRequestValue> actionRequestValues,
526            List<ActionRequestValue> nonRoleRequests) {
527
528        if (!CollectionUtils.isEmpty(actionRequestValues)) {
529            for (ActionRequestValue request : actionRequestValues) if (request.getDelegationType() == null) {
530                if (!CollectionUtils.isEmpty(request.getChildrenRequests())) {
531                    findNonRoleRequests(request.getChildrenRequests(), nonRoleRequests);
532                } else  {
533                    // see if we have a principal request
534                    if (RecipientType.ROLE.getCode() != request.getRecipientTypeCd()) {
535                        nonRoleRequests.add(request);
536                    }
537                }
538            }
539        }
540
541        return nonRoleRequests;
542    }
543
544
545    /**
546     * A simple class used to hold context during the PeopleFlow action request generation process.  Construction of
547     * the context will validate that the given values are valid, non-null values where appropriate.
548     */
549    final class Context {
550
551        private final RouteContext routeContext;
552        private final PeopleFlowDefinition peopleFlow;
553        private final ActionRequestType actionRequested;
554        private final ActionRequestFactory actionRequestFactory;
555
556        // lazily loaded
557        private PeopleFlowTypeService peopleFlowTypeService;
558        private boolean peopleFlowTypeServiceLoaded = false;
559        private String peopleFlowTypeServiceVersion;
560
561        Context(RouteContext routeContext, PeopleFlowDefinition peopleFlow, ActionRequestType actionRequested) {
562            if (routeContext == null) {
563                throw new IllegalArgumentException("routeContext was null");
564            }
565            if (peopleFlow == null) {
566                throw new IllegalArgumentException("peopleFlow was null");
567            }
568            if (!peopleFlow.isActive()) {
569                throw new ConfigurationException("Attempted to route to a PeopleFlow that is not active! " + peopleFlow);
570            }
571            if (actionRequested == null) {
572                actionRequested = ActionRequestType.APPROVE;
573            }
574            this.routeContext = routeContext;
575            this.peopleFlow = peopleFlow;
576            this.actionRequested = actionRequested;
577            this.actionRequestFactory = new ActionRequestFactory(routeContext);
578        }
579
580        RouteContext getRouteContext() {
581            return routeContext;
582        }
583
584        PeopleFlowDefinition getPeopleFlow() {
585            return peopleFlow;
586        }
587
588        ActionRequestType getActionRequested() {
589            return actionRequested;
590        }
591
592        ActionRequestFactory getActionRequestFactory() {
593            return actionRequestFactory;
594        }
595
596        /**
597         * Lazily loads and caches the {@code PeopleFlowTypeService} (if necessary) and returns it.
598         */
599        PeopleFlowTypeService getPeopleFlowTypeService() {
600
601            if (peopleFlowTypeServiceLoaded) {
602                return this.peopleFlowTypeService;
603            }
604
605            if (getPeopleFlow().getTypeId() != null) {
606                KewTypeDefinition typeDefinition = getTypeRepositoryService().getTypeById(getPeopleFlow().getTypeId());
607
608                if (typeDefinition == null) {
609                    throw new IllegalStateException("Failed to locate a PeopleFlow type for the given type id of '" + getPeopleFlow().getTypeId() + "'");
610                }
611
612                if (StringUtils.isNotBlank(typeDefinition.getServiceName())) {
613                    Endpoint endpoint = KsbApiServiceLocator.getServiceBus().getEndpoint(QName.valueOf(typeDefinition.getServiceName()));
614
615                    if (endpoint == null) {
616                        throw new IllegalStateException("Failed to load the PeopleFlowTypeService with the name '" + typeDefinition.getServiceName() + "'");
617                    }
618
619                    this.peopleFlowTypeService = (PeopleFlowTypeService)endpoint.getService();
620                    this.peopleFlowTypeServiceVersion = endpoint.getServiceConfiguration().getServiceVersion();
621                }
622            }
623            peopleFlowTypeServiceLoaded = true;
624            return this.peopleFlowTypeService;
625        }
626
627        String getPeopleFlowTypeServiceVersion() {
628            if (!this.peopleFlowTypeServiceLoaded) {
629                // execute getPeopleFlowTypeService first to lazy load
630                getPeopleFlowTypeService();
631            }
632
633            return this.peopleFlowTypeServiceVersion;
634        }
635    }
636}