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.config.ConfigurationException;
021import org.kuali.rice.core.api.membership.MemberType;
022import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
023import org.kuali.rice.kew.actionrequest.ActionRequestFactory;
024import org.kuali.rice.kew.actionrequest.ActionRequestValue;
025import org.kuali.rice.kew.actionrequest.KimGroupRecipient;
026import org.kuali.rice.kew.actionrequest.KimPrincipalRecipient;
027import org.kuali.rice.kew.actionrequest.Recipient;
028import org.kuali.rice.kew.api.action.ActionRequestType;
029import org.kuali.rice.kew.api.document.Document;
030import org.kuali.rice.kew.api.document.DocumentContent;
031import org.kuali.rice.kew.api.peopleflow.PeopleFlowDefinition;
032import org.kuali.rice.kew.api.peopleflow.PeopleFlowDelegate;
033import org.kuali.rice.kew.api.peopleflow.PeopleFlowMember;
034import org.kuali.rice.kew.api.repository.type.KewTypeDefinition;
035import org.kuali.rice.kew.api.repository.type.KewTypeRepositoryService;
036import org.kuali.rice.kew.engine.RouteContext;
037import org.kuali.rice.kew.framework.peopleflow.PeopleFlowTypeService;
038import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
039import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValueContent;
040import org.kuali.rice.kim.api.role.Role;
041import org.kuali.rice.kim.api.role.RoleMembership;
042import org.kuali.rice.kim.api.role.RoleService;
043
044import javax.xml.namespace.QName;
045import java.util.Collections;
046import java.util.List;
047import 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 */
055public 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}