001    /**
002     * Copyright 2005-2013 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.lang.StringUtils;
019    import org.kuali.rice.core.api.config.ConfigurationException;
020    import org.kuali.rice.core.api.exception.RiceRuntimeException;
021    import org.kuali.rice.core.api.util.xml.XmlJotter;
022    import org.kuali.rice.kew.actionrequest.ActionRequestValue;
023    import org.kuali.rice.kew.api.action.ActionRequestType;
024    import org.kuali.rice.kew.api.peopleflow.PeopleFlowDefinition;
025    import org.kuali.rice.kew.api.peopleflow.PeopleFlowService;
026    import org.kuali.rice.kew.engine.RouteContext;
027    import org.kuali.rice.kew.engine.node.NodeState;
028    import org.kuali.rice.kew.engine.node.RouteNodeUtils;
029    import org.kuali.rice.kew.api.exception.WorkflowException;
030    import org.kuali.rice.kew.routemodule.RouteModule;
031    import org.kuali.rice.kew.service.KEWServiceLocator;
032    import org.kuali.rice.kew.util.ResponsibleParty;
033    import org.w3c.dom.Element;
034    
035    import javax.xml.bind.JAXBContext;
036    import javax.xml.bind.JAXBException;
037    import javax.xml.bind.annotation.XmlAttribute;
038    import javax.xml.bind.annotation.XmlElement;
039    import javax.xml.bind.annotation.XmlElements;
040    import javax.xml.bind.annotation.XmlRootElement;
041    import javax.xml.bind.annotation.XmlValue;
042    import java.util.ArrayList;
043    import java.util.List;
044    
045    public class PeopleFlowRouteModule implements RouteModule {
046    
047        private static final String PEOPLE_FLOW_PROPERTY = "peopleFlow";
048    
049        private static final String PEOPLE_FLOW_ITERATION_KEY = "peopleFlowIteration";
050        public static final String PEOPLE_FLOW_SEQUENCE = "peopleFlowSequence";
051    
052        private static final JAXBContext jaxbContext;
053        static {
054            try {
055                jaxbContext = JAXBContext.newInstance(PeopleFlowConfig.class);
056            } catch (JAXBException e) {
057                throw new RiceRuntimeException("Failed to initialize JAXB!", e);
058            }
059        }
060    
061        private PeopleFlowService peopleFlowService;
062        private PeopleFlowRequestGenerator peopleFlowRequestGenerator;
063    
064        @Override
065        public List<ActionRequestValue> findActionRequests(RouteContext context) throws Exception {
066            int iteration = incrementIteration(context);
067            List<PeopleFlowConfig> configurations = parsePeopleFlowConfiguration(context);
068            List<ActionRequestValue> actionRequests = new ArrayList<ActionRequestValue>();
069            int index = 0;
070            for (PeopleFlowConfig configuration : configurations) {
071                if (index++ == iteration) {
072                    PeopleFlowDefinition peopleFlow = loadPeopleFlow(configuration);
073                    actionRequests.addAll(getPeopleFlowRequestGenerator().generateRequests(context, peopleFlow, configuration.actionRequested));
074                    break;
075                }
076            }
077            return actionRequests;
078        }
079    
080        @Override
081        public boolean isMoreRequestsAvailable(RouteContext context) {
082            Integer currentIteration = getCurrentIteration(context);
083            if (currentIteration == null) {
084                return false;
085            }
086            List<PeopleFlowConfig> configurations = parsePeopleFlowConfiguration(context);
087            return currentIteration.intValue() < configurations.size();
088        }
089    
090        @Override
091        public ResponsibleParty resolveResponsibilityId(String responsibilityId) throws WorkflowException {
092            return null;
093        }
094    
095        protected List<PeopleFlowConfig> parsePeopleFlowConfiguration(RouteContext context) {
096            List<PeopleFlowConfig> configs = new ArrayList<PeopleFlowConfig>();
097            NodeState peopleFlowSequenceNodeState = context.getNodeInstance().getNodeState(PEOPLE_FLOW_SEQUENCE);
098            if (peopleFlowSequenceNodeState != null) {
099                String peopleFlowSequence = peopleFlowSequenceNodeState.getValue();
100                if (StringUtils.isNotBlank(peopleFlowSequence)) {
101                    String[] peopleFlowValues = peopleFlowSequence.split(",");
102                    for (String peopleFlowValue : peopleFlowValues) {
103                        String[] peopleFlowProperties = peopleFlowValue.split(":");
104                        PeopleFlowConfig config = new PeopleFlowConfig();
105                        config.actionRequested = ActionRequestType.fromCode(peopleFlowProperties[0]);
106                        config.peopleFlowIdentifier = peopleFlowProperties[1];
107                        configs.add(config);
108                    }
109                }
110            } else {
111                List<Element> peopleFlowElements = RouteNodeUtils.getCustomRouteNodeElements(
112                        context.getNodeInstance().getRouteNode(), PEOPLE_FLOW_PROPERTY);
113    
114                for (Element peopleFlowElement : peopleFlowElements) {
115                    try {
116                        PeopleFlowConfig config = (PeopleFlowConfig)jaxbContext.createUnmarshaller().unmarshal(peopleFlowElement);
117                        if (config.actionRequested == null) {
118                            // default action request type to approve
119                            config.actionRequested = ActionRequestType.APPROVE;
120                        }
121                        if (config == null) {
122                            throw new IllegalStateException("People flow configuration element did not properly unmarshall from XML: " + XmlJotter.jotNode(peopleFlowElement));
123                        }
124                        configs.add(config);
125                    } catch (JAXBException e) {
126                        throw new RiceRuntimeException("Failed to unmarshall people flow configuration from route node.", e);
127                    }
128                }
129            }
130            return configs;
131        }
132    
133        protected PeopleFlowDefinition loadPeopleFlow(PeopleFlowConfig configuration) {
134            if (configuration.isId()) {
135                PeopleFlowDefinition peopleFlow = getPeopleFlowService().getPeopleFlow(configuration.getId());
136                if (peopleFlow == null) {
137                    throw new ConfigurationException("Failed to locate a people flow with the given id of '" + configuration.getId() + "'");
138                }
139                return peopleFlow;
140            } else {
141                String namespaceCode = configuration.getName().namespaceCode;
142                String name = configuration.getName().name;
143                PeopleFlowDefinition peopleFlow = getPeopleFlowService().getPeopleFlowByName(namespaceCode, name);
144                if (peopleFlow == null) {
145                    throw new ConfigurationException("Failed to locate a people flow with the given namespaceCode of '" + namespaceCode + "' and name of '" + name + "'");
146                }
147                return peopleFlow;
148            }
149        }
150    
151        protected int incrementIteration(RouteContext context) {
152            int nextIteration = 0;
153            NodeState nodeState = context.getNodeInstance().getNodeState(PEOPLE_FLOW_ITERATION_KEY);
154            if (nodeState == null) {
155                nodeState = new NodeState();
156                nodeState.setNodeInstance(context.getNodeInstance());
157                nodeState.setKey(PEOPLE_FLOW_ITERATION_KEY);
158                context.getNodeInstance().addNodeState(nodeState);
159            } else {
160                int currentIteration = Integer.parseInt(nodeState.getValue());
161                nextIteration = currentIteration + 1;
162            }
163            nodeState.setValue(Integer.toString(nextIteration));
164            if (!context.isSimulation()) {
165                KEWServiceLocator.getRouteNodeService().save(nodeState);
166            }
167            return nextIteration;
168        }
169    
170        protected Integer getCurrentIteration(RouteContext context) {
171            NodeState nodeState = context.getNodeInstance().getNodeState(PEOPLE_FLOW_ITERATION_KEY);
172            if (nodeState == null) {
173                return null;
174            }
175            return Integer.valueOf(nodeState.getValue());
176        }
177    
178        public PeopleFlowService getPeopleFlowService() {
179            return peopleFlowService;
180        }
181    
182        public void setPeopleFlowService(PeopleFlowService peopleFlowService) {
183            this.peopleFlowService = peopleFlowService;
184        }
185    
186        public PeopleFlowRequestGenerator getPeopleFlowRequestGenerator() {
187            return peopleFlowRequestGenerator;
188        }
189    
190        public void setPeopleFlowRequestGenerator(PeopleFlowRequestGenerator peopleFlowRequestGenerator) {
191            this.peopleFlowRequestGenerator = peopleFlowRequestGenerator;
192        }
193    
194        @XmlRootElement(name = "peopleFlow")
195        private static class PeopleFlowConfig {
196    
197            @XmlElement(name = "actionRequested")
198            ActionRequestType actionRequested;
199    
200            @XmlElements(value = {
201                @XmlElement(name = "name", type = NameConfig.class),
202                @XmlElement(name = "id", type = String.class)
203            })
204            Object peopleFlowIdentifier;
205    
206            boolean isId() {
207                return peopleFlowIdentifier instanceof String;
208            }
209            boolean isName() {
210                return peopleFlowIdentifier instanceof NameConfig;
211            }
212    
213            NameConfig getName() {
214                return (NameConfig)peopleFlowIdentifier;
215            }
216    
217            String getId() {
218                return (String)peopleFlowIdentifier;
219            }
220            
221        }
222    
223        private static class NameConfig {
224    
225            @XmlAttribute(name = "namespace")
226            String namespaceCode;
227    
228            @XmlValue
229            String name;
230    
231        }
232    
233    }