View Javadoc
1   /**
2    * Copyright 2004-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kew.impl.peopleflow;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.CoreConstants;
21  import org.kuali.rice.core.api.config.ConfigurationException;
22  import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
23  import org.kuali.rice.core.api.exception.RiceIllegalStateException;
24  import org.kuali.rice.core.api.membership.MemberType;
25  import org.kuali.rice.core.api.util.VersionHelper;
26  import org.kuali.rice.kew.actionrequest.ActionRequestFactory;
27  import org.kuali.rice.kew.actionrequest.ActionRequestValue;
28  import org.kuali.rice.kew.actionrequest.KimGroupRecipient;
29  import org.kuali.rice.kew.actionrequest.KimPrincipalRecipient;
30  import org.kuali.rice.kew.actionrequest.Recipient;
31  import org.kuali.rice.kew.api.action.ActionRequestPolicy;
32  import org.kuali.rice.kew.api.action.ActionRequestType;
33  import org.kuali.rice.kew.api.action.RecipientType;
34  import org.kuali.rice.kew.api.document.Document;
35  import org.kuali.rice.kew.api.document.DocumentContent;
36  import org.kuali.rice.kew.api.peopleflow.PeopleFlowDefinition;
37  import org.kuali.rice.kew.api.peopleflow.PeopleFlowDelegate;
38  import org.kuali.rice.kew.api.peopleflow.PeopleFlowMember;
39  import org.kuali.rice.kew.api.repository.type.KewTypeDefinition;
40  import org.kuali.rice.kew.api.repository.type.KewTypeRepositoryService;
41  import org.kuali.rice.kew.engine.RouteContext;
42  import org.kuali.rice.kew.framework.peopleflow.PeopleFlowTypeService;
43  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
44  import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValueContent;
45  import org.kuali.rice.kim.api.group.Group;
46  import org.kuali.rice.kim.api.identity.principal.Principal;
47  import org.kuali.rice.kim.api.role.Role;
48  import org.kuali.rice.kim.api.role.RoleMembership;
49  import org.kuali.rice.kim.api.role.RoleService;
50  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
51  import org.kuali.rice.ksb.api.KsbApiServiceLocator;
52  import org.kuali.rice.ksb.api.bus.Endpoint;
53  
54  import javax.xml.namespace.QName;
55  import java.util.ArrayList;
56  import java.util.Collections;
57  import java.util.List;
58  import java.util.Map;
59  
60  /**
61   * Reference implementation of the {@code PeopleFlowRequestGenerator} which is responsible for generating Action
62   * Requests from a {@link PeopleFlowDefinition}.
63   *
64   * @author Kuali Rice Team (rice.collab@kuali.org)
65   */
66  public class PeopleFlowRequestGeneratorImpl implements PeopleFlowRequestGenerator {
67  
68      private KewTypeRepositoryService typeRepositoryService;
69      private RoleService roleService;
70  
71      @Override
72      public List<ActionRequestValue> generateRequests(RouteContext routeContext, PeopleFlowDefinition peopleFlow, ActionRequestType actionRequested) {
73          Context context = new Context(routeContext, peopleFlow, actionRequested);
74          for (PeopleFlowMember member : peopleFlow.getMembers()) {
75              generateRequestForMember(context, member);
76          }
77  
78          return context.getActionRequestFactory().getRequestGraphs();
79      }
80  
81      protected void generateRequestForMember(Context context, PeopleFlowMember member) {
82          // used later for generating any delegate requests
83          List<ActionRequestValue> memberRequests = new ArrayList<ActionRequestValue>();
84  
85          if (MemberType.ROLE == member.getMemberType()) {
86              memberRequests.addAll(findNonRoleRequests(generateRequestsForRoleMember(context, member)));
87          } else {
88              ActionRequestValue actionRequest = context.getActionRequestFactory().addRootActionRequest(
89                      context.getActionRequested().getCode(), member.getPriority(), toRecipient(member), "",
90                      member.getResponsibilityId(), Boolean.TRUE, getActionRequestPolicyCode(member), null);
91  
92              if (actionRequest != null) {
93                  memberRequests.add(actionRequest);
94              }
95          }
96  
97          // KULRICE-5726: Add support for delegates on roles in PeopleFlows as well as using roles as delegates
98          generateDelegationRequests(context, memberRequests, member);
99      }
100 
101     /**
102      * generates requests for a PeopleFlow member of type Role.
103      *
104      * <p>Will resolve role qualifiers through the appropriate PeopleFlowTypeService, and if multiple qualifier maps are
105      * generated, it can generate multiple request trees.
106      * parent requests</p>
107      *
108      * @param context the context for request generation
109      * @param member the PeopleFlow member, which is assumed to be of MemberType ROLE
110      * @return a list of root action requests that were generated, or an empty list if no role members were resolved
111      */
112     protected List<ActionRequestValue> generateRequestsForRoleMember(Context context, PeopleFlowMember member) {
113         List<ActionRequestValue> roleMemberRequests = new ArrayList<ActionRequestValue>(); // results
114 
115         List<Map<String, String>> roleQualifierMaps = loadRoleQualifiers(context, member.getMemberId());
116         Role role = getRoleService().getRole(member.getMemberId());
117 
118         boolean hasPeopleFlowDelegates = !CollectionUtils.isEmpty(member.getDelegates());
119 
120         if (role == null) {
121             throw new IllegalStateException("Failed to locate a role with the given role id of '" +
122                     member.getMemberId() + "'");
123         }
124 
125         if (CollectionUtils.isEmpty(roleQualifierMaps)) {
126             ActionRequestValue request = addKimRoleRequest(context, member, role, Collections.<String, String>emptyMap(),
127                     hasPeopleFlowDelegates);
128 
129             if (request != null) {
130                 roleMemberRequests.add(request);
131             }
132         } else {
133             // we may have multiple maps of role qualifiers, so we'll add a request for each map
134             for (Map<String, String> roleQualifiers : roleQualifierMaps) {
135                 ActionRequestValue request = addKimRoleRequest(context, member, role, roleQualifiers,
136                         hasPeopleFlowDelegates);
137                 if (request != null) {
138                     roleMemberRequests.add(request);
139                 }
140             }
141         }
142 
143         return roleMemberRequests;
144     }
145 
146     /**
147      * Uses the ActionRequestFactory to build the request tree for the role members.
148      *
149      * <p>The role members themselves are derived here using the given qualifiers.</p>
150      *
151      * @param context the context for request generation
152      * @param member the PeopleFlow member
153      * @param role the role specified within the member
154      * @param roleQualifiers the qualifiers to use for role member selection
155      * @param ignoreKimDelegates should KIM delegates be ignored when generating requests?
156      * @return the root request of the generated action request tree, or null if no members are found
157      */
158     private ActionRequestValue addKimRoleRequest(Context context, PeopleFlowMember member, Role role,
159                                                  Map<String, String> roleQualifiers, boolean ignoreKimDelegates) {
160 
161         ActionRequestValue roleMemberRequest = null;
162 
163         List<RoleMembership> memberships = getRoleService().getRoleMembers(Collections.singletonList(
164                 member.getMemberId()), roleQualifiers);
165 
166         String actionRequestPolicyCode = getActionRequestPolicyCode(member);
167 
168         if (!CollectionUtils.isEmpty(memberships)) {
169             roleMemberRequest = context.getActionRequestFactory().addKimRoleRequest(
170                     context.getActionRequested().getCode(), member.getPriority(), role, memberships, null,
171                     member.getResponsibilityId(), true, actionRequestPolicyCode, null, ignoreKimDelegates);
172             //memberships.get(0).getQualifier()
173         }
174 
175         return roleMemberRequest;
176     }
177 
178     /**
179      * Generates any needed requests for {@link PeopleFlowDelegate}s on the given member.
180      *
181      * <p>If there are no delegates, or if no requests were generated for the member, then this will be a no-op.</p>
182      *
183      * @param context the context for request generation
184      * @param memberRequests any action requests that were generated for the given member
185      * @param member the PeopleFlow member
186      */
187     private void generateDelegationRequests(Context context, List<ActionRequestValue> memberRequests,
188                                             PeopleFlowMember member) {
189 
190         if (CollectionUtils.isEmpty(member.getDelegates()) || CollectionUtils.isEmpty(memberRequests)) {
191             return;
192         }
193 
194         /*for (PeopleFlowDelegate delegate : member.getDelegates()) {
195             if (MemberType.ROLE == delegate.getMemberType()) {
196                 generateDelegationToRoleRequests(context, memberRequest, member, delegate);
197             } else {
198                 for (ActionRequestValue memberRequest : memberRequests) {
199                     generateDelegationToNonRoleRequest(context, memberRequest, member, delegate);
200                 }
201             }
202         }*/
203         /*List<RoleMembership> memberships = getRoleService().getRoleMembers(Collections.singletonList(
204                 member.getMemberId()), roleQualifiers);*/
205         for (PeopleFlowDelegate delegate : member.getDelegates()) {
206             for (ActionRequestValue memberRequest : memberRequests) {
207                 if (MemberType.ROLE == delegate.getMemberType()) {
208                     generateDelegationToRoleRequests(context, memberRequest, member, delegate);
209                 } else {
210                     generateDelegationToNonRoleRequest(context, memberRequest, member, delegate);
211                 }
212             }
213         }
214     }
215 
216     /**
217      * Uses the ActionRequestFactory to add the delegate request to the given parent request.
218      *
219      * <p>Only handles non-role delegates.  If a delegate of type role is passed, a RiceIllegalStateException will be
220      * thrown.</p>
221      *
222      * @param context the context for request generation
223      * @param memberRequest an action request that was generated for the given member
224      * @param member the PeopleFlow member
225      * @param delegate the delegate to generate a request to
226      */
227     private void generateDelegationToNonRoleRequest(Context context, ActionRequestValue memberRequest,
228                                                     PeopleFlowMember member, PeopleFlowDelegate delegate) {
229 
230         Recipient recipient;
231 
232         if (MemberType.PRINCIPAL == delegate.getMemberType()) {
233             recipient = new KimPrincipalRecipient(delegate.getMemberId());
234         } else if (MemberType.GROUP == delegate.getMemberType()) {
235             recipient = new KimGroupRecipient(delegate.getMemberId());
236         } else {
237             throw new RiceIllegalStateException("MemberType unknown: " + delegate.getMemberType());
238         }
239 
240         String actionRequestPolicyCode = getDelegateActionRequestPolicyCode(member, delegate);
241 
242         String delegationAnnotation = generateDelegationAnnotation(memberRequest, member, delegate);
243 
244         context.getActionRequestFactory().addDelegationRequest(memberRequest, recipient,
245                 delegate.getResponsibilityId(), memberRequest.getForceAction(),
246                 delegate.getDelegationType(), actionRequestPolicyCode, delegationAnnotation, null);
247     }
248 
249     /**
250      * Builds the String that will be used for the annotation on the delegate requests
251      *
252      * @param parentRequest an action request that was generated for the given member
253      * @param member the PeopleFlow member
254      * @param delegate the delegate
255      * @return the annotation string
256      */
257     private String generateDelegationAnnotation(ActionRequestValue parentRequest, PeopleFlowMember member,
258                                                 PeopleFlowDelegate delegate) {
259 
260         StringBuffer annotation = new StringBuffer( "Delegation of: " );
261         annotation.append( parentRequest.getAnnotation() );
262         annotation.append( " to " );
263 
264         if (delegate.getMemberType() == MemberType.PRINCIPAL) {
265             annotation.append( "principal " );
266             Principal principal = KimApiServiceLocator.getIdentityService().getPrincipal(delegate.getMemberId());
267 
268             if ( principal != null ) {
269                 annotation.append( principal.getPrincipalName() );
270             } else {
271                 annotation.append( member.getMemberId() );
272             }
273         } else if (delegate.getMemberType() == MemberType.GROUP) {
274             annotation.append( "group " );
275             Group group = KimApiServiceLocator.getGroupService().getGroup(delegate.getMemberId());
276 
277             if ( group != null ) {
278                 annotation.append( group.getNamespaceCode() ).append( '/' ).append( group.getName() );
279             } else {
280                 annotation.append( member.getMemberId() );
281             }
282         } else {
283             annotation.append( "?????? '" );
284             annotation.append( member.getMemberId() );
285             annotation.append( "'" );
286         }
287 
288         return annotation.toString();
289     }
290 
291 
292     /**
293      * Generates any needed requests for the given {@link PeopleFlowDelegate}.
294      *
295      * <p>It is assumed that the given member has the given delegate configured.</p>
296      *
297      * @param context the context for request generation
298      * @param parentRequest an action request that was generated for the given member
299      * @param member the PeopleFlow member, which should contain the given delegate
300      * @param delegate the delegate, assumed to be of MemberType ROLE, to generate a request to
301      */
302     protected void generateDelegationToRoleRequests(Context context,
303                                                     ActionRequestValue parentRequest, PeopleFlowMember member, PeopleFlowDelegate delegate) {
304 
305         List<Map<String, String>> roleQualifierList = loadRoleQualifiers(context, delegate.getMemberId());
306         Role role = getRoleService().getRole(delegate.getMemberId());
307 
308         if (role == null) {
309             throw new IllegalStateException("Failed to locate a role with the given role id of '" +
310                     delegate.getMemberId() + "'");
311         }
312 
313         if (CollectionUtils.isEmpty(roleQualifierList)) {
314             addKimRoleDelegateRequest(context, parentRequest, member, delegate, role,
315                     Collections.<String, String>emptyMap());
316         } else {
317             for (Map<String, String> roleQualifiers : roleQualifierList) {
318                 List<RoleMembership> memberships = getRoleService().getRoleMembers(Collections.singletonList(
319                         member.getMemberId()), roleQualifiers);
320                 for (RoleMembership membership : memberships) {
321                     if (membership.getType().equals(MemberType.PRINCIPAL)
322                             && StringUtils.equals(membership.getMemberId(), parentRequest.getPrincipalId())) {
323                         addKimRoleDelegateRequest(context, parentRequest, member, delegate, role, roleQualifiers);
324                     } else if (membership.getType().equals(MemberType.GROUP)
325                             && StringUtils.equals(membership.getMemberId(), parentRequest.getGroupId())) {
326                         addKimRoleDelegateRequest(context, parentRequest, member, delegate, role, roleQualifiers);
327                     }
328                 }
329 
330             }
331         }
332     }
333 
334     /**
335      * Helper method uses the ActionRequestFactory to add to the parent request the delegation request(s) to a role.
336      *
337      * <p>The role members themselves are derived here using the given qualifiers.</p>
338      *
339      * @param context the context for request generation
340      * @param parentRequest an action request that was generated for the given member
341      * @param member the PeopleFlow member
342      * @param delegate the delegate to generate a request to
343      * @param role the role specified within the delegate
344      * @param roleQualifiers the qualifiers to use for role member selection
345      */
346     private void addKimRoleDelegateRequest(Context context, ActionRequestValue parentRequest,
347                                            PeopleFlowMember member, PeopleFlowDelegate delegate, Role role, Map<String, String> roleQualifiers) {
348 
349         // sanity check
350         if (delegate.getMemberType() != MemberType.ROLE) {
351             throw new RiceIllegalArgumentException("delegate's member type must be ROLE");
352         } else if (!delegate.getMemberId().equals(role.getId())) {
353             throw new RiceIllegalArgumentException("delegate's member id must match the given role's id");
354         }
355 
356         String actionRequestPolicyCode = getDelegateActionRequestPolicyCode(member, delegate);
357 
358         List<RoleMembership> memberships = getRoleService().getRoleMembers(Collections.singletonList(
359                 delegate.getMemberId()), roleQualifiers);
360 
361         if (!CollectionUtils.isEmpty(memberships)) {
362             context.getActionRequestFactory().addDelegateKimRoleRequest(parentRequest,
363                     delegate.getDelegationType(), context.getActionRequested().getCode(), member.getPriority(), role,
364                     memberships, null, delegate.getResponsibilityId(), true, actionRequestPolicyCode, null);
365         }
366     }
367 
368     /**
369      * Uses the appropriate {@link PeopleFlowTypeService} to get the role qualifier maps for the given document and
370      * role.
371      *
372      * <p>Note that the PeopleFlowTypeService is selected based on the type id of the PeopleFlow.</p>
373      *
374      * @param context the context for request generation
375      * @param roleId the ID of the role for whom qualifiers are being loaded
376      * @return the qualifier maps, or an empty list if there are none
377      */
378     protected List<Map<String, String>> loadRoleQualifiers(Context context, String roleId) {
379         PeopleFlowTypeService peopleFlowTypeService = context.getPeopleFlowTypeService();
380         List<Map<String, String>> roleQualifierList = new ArrayList<Map<String, String>>();
381 
382         if (peopleFlowTypeService != null) {
383             Document document = DocumentRouteHeaderValue.to(context.getRouteContext().getDocument());
384             DocumentRouteHeaderValueContent content = new DocumentRouteHeaderValueContent(document.getDocumentId());
385             content.setDocumentContent(context.getRouteContext().getDocumentContent().getDocContent());
386             DocumentContent documentContent = DocumentRouteHeaderValueContent.to(content);
387 
388             Map<String, String> roleQualifiers = peopleFlowTypeService.resolveRoleQualifiers(
389                     context.getPeopleFlow().getTypeId(), roleId, document, documentContent
390             );
391 
392             if (roleQualifiers != null) {
393                 roleQualifierList.add(roleQualifiers);
394             }
395 
396             boolean versionOk = VersionHelper.compareVersion(context.getPeopleFlowTypeServiceVersion(), CoreConstants.Versions.VERSION_2_3_0) != -1;
397             if(versionOk) {
398                 List<Map<String, String>> multipleRoleQualifiers = peopleFlowTypeService.resolveMultipleRoleQualifiers(
399                         context.getPeopleFlow().getTypeId(), roleId, document, documentContent);
400 
401                 if (multipleRoleQualifiers != null) {
402                     roleQualifierList.addAll(multipleRoleQualifiers);
403                 }
404             }
405 
406         }
407 
408         return roleQualifierList;
409     }
410 
411     /**
412      * Gets the action request policy code for the given delegate.
413      *
414      * <p>the delegate is considered first, and the member is used as a fallback.  May return null.</p>
415      *
416      * @param member the PeopleFlow member
417      * @param delegate the delegate
418      * @return the action request policy code, or null if none is found
419      */
420     private String getDelegateActionRequestPolicyCode(PeopleFlowMember member, PeopleFlowDelegate delegate) {
421         ActionRequestPolicy actionRequestPolicy = delegate.getActionRequestPolicy();
422 
423         return (actionRequestPolicy != null) ? actionRequestPolicy.getCode() : getActionRequestPolicyCode(member);
424     }
425 
426     /**
427      * Gets the action request policy code for the given member.
428      *
429      * @param member the PeopleFlow member
430      * @return the action request policy code, or null if none is found
431      */
432     private String getActionRequestPolicyCode(PeopleFlowMember member) {
433         ActionRequestPolicy actionRequestPolicy = member.getActionRequestPolicy();
434 
435         return (actionRequestPolicy != null) ? actionRequestPolicy.getCode() : null;
436     }
437 
438     private Recipient toRecipient(PeopleFlowMember member) {
439         Recipient recipient;
440         if (MemberType.PRINCIPAL == member.getMemberType()) {
441             recipient = new KimPrincipalRecipient(member.getMemberId());
442         } else if (MemberType.GROUP == member.getMemberType()) {
443             recipient = new KimGroupRecipient(member.getMemberId());
444         } else {
445             throw new IllegalStateException("encountered a member type which I did not understand: " +
446                     member.getMemberType());
447         }
448         return recipient;
449     }
450 
451     private Recipient toRecipient(PeopleFlowDelegate delegate) {
452         Recipient recipient;
453         if (MemberType.PRINCIPAL == delegate.getMemberType()) {
454             recipient = new KimPrincipalRecipient(delegate.getMemberId());
455         } else if (MemberType.GROUP == delegate.getMemberType()) {
456             recipient = new KimGroupRecipient(delegate.getMemberId());
457         } else {
458             throw new IllegalStateException("encountered a delegate member type which I did not understand: " +
459                     delegate.getMemberType());
460         }
461         return recipient;
462     }
463 
464     public KewTypeRepositoryService getTypeRepositoryService() {
465         return typeRepositoryService;
466     }
467 
468     public void setTypeRepositoryService(KewTypeRepositoryService typeRepositoryService) {
469         this.typeRepositoryService = typeRepositoryService;
470     }
471 
472     public RoleService getRoleService() {
473         return roleService;
474     }
475 
476     public void setRoleService(RoleService roleService) {
477         this.roleService = roleService;
478     }
479 
480     /**
481      * Recursively find all non-delegate Group and Principal requests from all of the requests in the given list.
482      *
483      * @param actionRequestValues the list of {@link ActionRequestValue}s to search
484      * @return a list of the non-delegate Group and Principal requests found
485      */
486     private List<ActionRequestValue> findNonRoleRequests(List<ActionRequestValue> actionRequestValues) {
487         List<ActionRequestValue> nonRoleRequests = new ArrayList<ActionRequestValue>();
488 
489         return findNonRoleRequests(actionRequestValues, nonRoleRequests);
490     }
491 
492     // Recursion helper method
493     private List<ActionRequestValue> findNonRoleRequests(List<ActionRequestValue> actionRequestValues,
494                                                          List<ActionRequestValue> nonRoleRequests) {
495 
496         if (!CollectionUtils.isEmpty(actionRequestValues)) {
497             for (ActionRequestValue request : actionRequestValues) if (request.getDelegationType() == null) {
498                 if (!CollectionUtils.isEmpty(request.getChildrenRequests())) {
499                     findNonRoleRequests(request.getChildrenRequests(), nonRoleRequests);
500                 } else  {
501                     // see if we have a principal request
502                     if (RecipientType.ROLE.getCode() != request.getRecipientTypeCd()) {
503                         nonRoleRequests.add(request);
504                     }
505                 }
506             }
507         }
508 
509         return nonRoleRequests;
510     }
511 
512 
513     /**
514      * A simple class used to hold context during the PeopleFlow action request generation process.  Construction of
515      * the context will validate that the given values are valid, non-null values where appropriate.
516      */
517     final class Context {
518 
519         private final RouteContext routeContext;
520         private final PeopleFlowDefinition peopleFlow;
521         private final ActionRequestType actionRequested;
522         private final ActionRequestFactory actionRequestFactory;
523 
524         // lazily loaded
525         private PeopleFlowTypeService peopleFlowTypeService;
526         private boolean peopleFlowTypeServiceLoaded = false;
527         private String peopleFlowTypeServiceVersion;
528 
529         Context(RouteContext routeContext, PeopleFlowDefinition peopleFlow, ActionRequestType actionRequested) {
530             if (routeContext == null) {
531                 throw new IllegalArgumentException("routeContext was null");
532             }
533             if (peopleFlow == null) {
534                 throw new IllegalArgumentException("peopleFlow was null");
535             }
536             if (!peopleFlow.isActive()) {
537                 throw new ConfigurationException("Attempted to route to a PeopleFlow that is not active! " + peopleFlow);
538             }
539             if (actionRequested == null) {
540                 actionRequested = ActionRequestType.APPROVE;
541             }
542             this.routeContext = routeContext;
543             this.peopleFlow = peopleFlow;
544             this.actionRequested = actionRequested;
545             this.actionRequestFactory = new ActionRequestFactory(routeContext);
546         }
547 
548         RouteContext getRouteContext() {
549             return routeContext;
550         }
551 
552         PeopleFlowDefinition getPeopleFlow() {
553             return peopleFlow;
554         }
555 
556         ActionRequestType getActionRequested() {
557             return actionRequested;
558         }
559 
560         ActionRequestFactory getActionRequestFactory() {
561             return actionRequestFactory;
562         }
563 
564         /**
565          * Lazily loads and caches the {@code PeopleFlowTypeService} (if necessary) and returns it.
566          */
567         PeopleFlowTypeService getPeopleFlowTypeService() {
568             if (peopleFlowTypeServiceLoaded) {
569                 return this.peopleFlowTypeService;
570             }
571 
572             if (getPeopleFlow().getTypeId() != null) {
573                 KewTypeDefinition typeDefinition = getTypeRepositoryService().getTypeById(getPeopleFlow().getTypeId());
574 
575                 if (typeDefinition == null) {
576                     throw new IllegalStateException("Failed to locate a PeopleFlow type for the given type id of '" + getPeopleFlow().getTypeId() + "'");
577                 }
578 
579                 if (StringUtils.isNotBlank(typeDefinition.getServiceName())) {
580                     Endpoint endpoint = KsbApiServiceLocator.getServiceBus().getEndpoint(QName.valueOf(typeDefinition.getServiceName()));
581 
582                     if (endpoint == null) {
583                         throw new IllegalStateException("Failed to load the PeopleFlowTypeService with the name '" + typeDefinition.getServiceName() + "'");
584                     }
585 
586                     this.peopleFlowTypeService = (PeopleFlowTypeService)endpoint.getService();
587                     this.peopleFlowTypeServiceVersion = endpoint.getServiceConfiguration().getServiceVersion();
588                 }
589             }
590             peopleFlowTypeServiceLoaded = true;
591             return this.peopleFlowTypeService;
592         }
593 
594         String getPeopleFlowTypeServiceVersion() {
595             if (!this.peopleFlowTypeServiceLoaded) {
596                 // execute getPeopleFlowTypeService first to lazy load
597                 getPeopleFlowTypeService();
598             }
599 
600             return this.peopleFlowTypeServiceVersion;
601         }
602     }
603 }