001 /** 002 * Copyright 2005-2012 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.rice.kew.impl.peopleflow; 017 018 import org.apache.commons.collections.CollectionUtils; 019 import org.apache.commons.lang.StringUtils; 020 import org.kuali.rice.core.api.config.ConfigurationException; 021 import org.kuali.rice.core.api.membership.MemberType; 022 import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader; 023 import org.kuali.rice.kew.actionrequest.ActionRequestFactory; 024 import org.kuali.rice.kew.actionrequest.ActionRequestValue; 025 import org.kuali.rice.kew.actionrequest.KimGroupRecipient; 026 import org.kuali.rice.kew.actionrequest.KimPrincipalRecipient; 027 import org.kuali.rice.kew.actionrequest.Recipient; 028 import org.kuali.rice.kew.api.action.ActionRequestType; 029 import org.kuali.rice.kew.api.document.Document; 030 import org.kuali.rice.kew.api.document.DocumentContent; 031 import org.kuali.rice.kew.api.peopleflow.PeopleFlowDefinition; 032 import org.kuali.rice.kew.api.peopleflow.PeopleFlowDelegate; 033 import org.kuali.rice.kew.api.peopleflow.PeopleFlowMember; 034 import org.kuali.rice.kew.api.repository.type.KewTypeDefinition; 035 import org.kuali.rice.kew.api.repository.type.KewTypeRepositoryService; 036 import org.kuali.rice.kew.engine.RouteContext; 037 import org.kuali.rice.kew.framework.peopleflow.PeopleFlowTypeService; 038 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue; 039 import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValueContent; 040 import org.kuali.rice.kim.api.role.Role; 041 import org.kuali.rice.kim.api.role.RoleMembership; 042 import org.kuali.rice.kim.api.role.RoleService; 043 044 import javax.xml.namespace.QName; 045 import java.util.Collections; 046 import java.util.List; 047 import java.util.Map; 048 049 /** 050 * Reference implementation of the {@code PeopleFlowRequestGenerator} which is responsible for generating Action 051 * Requests from a {@link PeopleFlowDefinition}. 052 * 053 * @author Kuali Rice Team (rice.collab@kuali.org) 054 */ 055 public class PeopleFlowRequestGeneratorImpl implements PeopleFlowRequestGenerator { 056 057 private KewTypeRepositoryService typeRepositoryService; 058 private RoleService roleService; 059 060 @Override 061 public List<ActionRequestValue> generateRequests(RouteContext routeContext, PeopleFlowDefinition peopleFlow, ActionRequestType actionRequested) { 062 Context context = new Context(routeContext, peopleFlow, actionRequested); 063 for (PeopleFlowMember member : peopleFlow.getMembers()) { 064 generateRequestForMember(context, member); 065 } 066 return context.getActionRequestFactory().getRequestGraphs(); 067 } 068 069 protected void generateRequestForMember(Context context, PeopleFlowMember member) { 070 String actionRequestPolicyCode = null; 071 if (member.getActionRequestPolicy() != null) { 072 actionRequestPolicyCode = member.getActionRequestPolicy().getCode(); 073 } 074 if (MemberType.ROLE == member.getMemberType()) { 075 generateRequestForRoleMember(context, member, actionRequestPolicyCode); 076 } else { 077 ActionRequestValue actionRequest = context.getActionRequestFactory().addRootActionRequest( 078 context.getActionRequested().getCode(), member.getPriority(), toRecipient(member), "", 079 member.getResponsibilityId(), Boolean.TRUE, actionRequestPolicyCode, null); 080 if (CollectionUtils.isNotEmpty(member.getDelegates())) { 081 for (PeopleFlowDelegate delegate : member.getDelegates()) { 082 context.getActionRequestFactory().addDelegationRequest(actionRequest, toRecipient(delegate), 083 delegate.getResponsibilityId(), Boolean.TRUE, delegate.getDelegationType(), "", null); 084 } 085 } 086 } 087 } 088 089 protected void generateRequestForRoleMember(Context context, PeopleFlowMember member, String actionRequestPolicyCode) { 090 Map<String, String> roleQualifiers = loadRoleQualifiers(context, member); 091 Role role = getRoleService().getRole(member.getMemberId()); 092 if (role == null) { 093 throw new IllegalStateException("Failed to locate a role with the given role id of '" + member.getMemberId() + "'"); 094 } 095 List<RoleMembership> memberships = getRoleService().getRoleMembers(Collections.singletonList( 096 member.getMemberId()), roleQualifiers); 097 if (!CollectionUtils.isEmpty(memberships)) { 098 context.getActionRequestFactory().addKimRoleRequest(context.getActionRequested().getCode(), member.getPriority(), 099 role, memberships, null, member.getResponsibilityId(), true, actionRequestPolicyCode, null); 100 } 101 // TODO - KULRICE-5726 - still need to implement support for ignoring built-in kim delegates whenever peopleflow delegate(s) are defined 102 } 103 104 protected Map<String, String> loadRoleQualifiers(Context context, PeopleFlowMember member) { 105 PeopleFlowTypeService peopleFlowTypeService = context.getPeopleFlowTypeService(); 106 if (peopleFlowTypeService != null) { 107 Document document = DocumentRouteHeaderValue.to(context.getRouteContext().getDocument()); 108 DocumentRouteHeaderValueContent content = new DocumentRouteHeaderValueContent(document.getDocumentId()); 109 content.setDocumentContent(context.getRouteContext().getDocumentContent().getDocContent()); 110 DocumentContent documentContent = DocumentRouteHeaderValueContent.to(content); 111 Map<String, String> roleQualifiers = peopleFlowTypeService.resolveRoleQualifiers( 112 context.getPeopleFlow().getTypeId(), member.getMemberId(), document, documentContent); 113 if (roleQualifiers != null) { 114 return roleQualifiers; 115 } 116 } 117 return Collections.emptyMap(); 118 } 119 120 private Recipient toRecipient(PeopleFlowMember member) { 121 Recipient recipient; 122 if (MemberType.PRINCIPAL == member.getMemberType()) { 123 recipient = new KimPrincipalRecipient(member.getMemberId()); 124 } else if (MemberType.GROUP == member.getMemberType()) { 125 recipient = new KimGroupRecipient(member.getMemberId()); 126 } else { 127 throw new IllegalStateException("encountered a member type which I did not understand: " + 128 member.getMemberType()); 129 } 130 return recipient; 131 } 132 133 private Recipient toRecipient(PeopleFlowDelegate delegate) { 134 Recipient recipient; 135 if (MemberType.PRINCIPAL == delegate.getMemberType()) { 136 recipient = new KimPrincipalRecipient(delegate.getMemberId()); 137 } else if (MemberType.GROUP == delegate.getMemberType()) { 138 recipient = new KimGroupRecipient(delegate.getMemberId()); 139 } else { 140 throw new IllegalStateException("encountered a delegate member type which I did not understand: " + 141 delegate.getMemberType()); 142 } 143 return recipient; 144 } 145 146 public KewTypeRepositoryService getTypeRepositoryService() { 147 return typeRepositoryService; 148 } 149 150 public void setTypeRepositoryService(KewTypeRepositoryService typeRepositoryService) { 151 this.typeRepositoryService = typeRepositoryService; 152 } 153 154 public RoleService getRoleService() { 155 return roleService; 156 } 157 158 public void setRoleService(RoleService roleService) { 159 this.roleService = roleService; 160 } 161 162 /** 163 * A simple class used to hold context during the PeopleFlow action request generation process. Construction of 164 * the context will validate that the given values are valid, non-null values where appropriate. 165 */ 166 final class Context { 167 168 private final RouteContext routeContext; 169 private final PeopleFlowDefinition peopleFlow; 170 private final ActionRequestType actionRequested; 171 private final ActionRequestFactory actionRequestFactory; 172 173 // lazily loaded 174 private PeopleFlowTypeService peopleFlowTypeService; 175 private boolean peopleFlowTypeServiceLoaded = false; 176 177 Context(RouteContext routeContext, PeopleFlowDefinition peopleFlow, ActionRequestType actionRequested) { 178 if (routeContext == null) { 179 throw new IllegalArgumentException("routeContext was null"); 180 } 181 if (peopleFlow == null) { 182 throw new IllegalArgumentException("peopleFlow was null"); 183 } 184 if (!peopleFlow.isActive()) { 185 throw new ConfigurationException("Attempted to route to a PeopleFlow that is not active! " + peopleFlow); 186 } 187 if (actionRequested == null) { 188 actionRequested = ActionRequestType.APPROVE; 189 } 190 this.routeContext = routeContext; 191 this.peopleFlow = peopleFlow; 192 this.actionRequested = actionRequested; 193 this.actionRequestFactory = new ActionRequestFactory(routeContext); 194 } 195 196 RouteContext getRouteContext() { 197 return routeContext; 198 } 199 200 PeopleFlowDefinition getPeopleFlow() { 201 return peopleFlow; 202 } 203 204 ActionRequestType getActionRequested() { 205 return actionRequested; 206 } 207 208 ActionRequestFactory getActionRequestFactory() { 209 return actionRequestFactory; 210 } 211 212 /** 213 * Lazily loads and caches the {@code PeopleFlowTypeService} (if necessary) and returns it. 214 */ 215 PeopleFlowTypeService getPeopleFlowTypeService() { 216 if (peopleFlowTypeServiceLoaded) { 217 return this.peopleFlowTypeService; 218 } 219 if (getPeopleFlow().getTypeId() != null) { 220 KewTypeDefinition typeDefinition = getTypeRepositoryService().getTypeById(getPeopleFlow().getTypeId()); 221 if (typeDefinition == null) { 222 throw new IllegalStateException("Failed to locate a PeopleFlow type for the given type id of '" + getPeopleFlow().getTypeId() + "'"); 223 } 224 if (StringUtils.isNotBlank(typeDefinition.getServiceName())) { 225 this.peopleFlowTypeService = GlobalResourceLoader.getService(QName.valueOf(typeDefinition.getServiceName())); 226 if (this.peopleFlowTypeService == null) { 227 throw new IllegalStateException("Failed to load the PeopleFlowTypeService with the name '" + typeDefinition.getServiceName() + "'"); 228 } 229 } 230 } 231 peopleFlowTypeServiceLoaded = true; 232 return this.peopleFlowTypeService; 233 } 234 235 } 236 }