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    }