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 }