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.support.krms;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.log4j.Logger;
020import org.kuali.rice.core.api.config.ConfigurationException;
021import org.kuali.rice.core.api.reflect.ObjectDefinition;
022import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
023import org.kuali.rice.kew.actionrequest.ActionRequestValue;
024import org.kuali.rice.kew.api.exception.WorkflowException;
025import org.kuali.rice.kew.api.extension.ExtensionDefinition;
026import org.kuali.rice.kew.api.extension.ExtensionRepositoryService;
027import org.kuali.rice.kew.api.extension.ExtensionUtils;
028import org.kuali.rice.kew.engine.RouteContext;
029import org.kuali.rice.kew.engine.node.NodeState;
030import org.kuali.rice.kew.engine.node.RouteNode;
031import org.kuali.rice.kew.engine.node.RouteNodeUtils;
032import org.kuali.rice.kew.engine.node.service.RouteNodeService;
033import org.kuali.rice.kew.framework.support.krms.RulesEngineExecutor;
034import org.kuali.rice.kew.impl.peopleflow.PeopleFlowRouteModule;
035import org.kuali.rice.kew.routemodule.RouteModule;
036import org.kuali.rice.kew.util.ResponsibleParty;
037import org.kuali.rice.krms.api.KrmsApiServiceLocator;
038import org.kuali.rice.krms.api.engine.Engine;
039import org.kuali.rice.krms.api.engine.EngineResults;
040import org.w3c.dom.Element;
041
042import java.util.List;
043
044/**
045 * An implementation of a {@link RouteModule} which executes the KRMS rules engine using the configured
046 * {@link RulesEngineExecutor}.  It then interprets those results and processes them, which may include instantiating
047 * and delegating to another RouteModule.  Currently, this implementation only supports PeopleFlow results returned from
048 * KRMS and passes those off to the {@link org.kuali.rice.kew.impl.peopleflow.PeopleFlowRouteModule}.
049 *
050 * @author Kuali Rice Team (rice.collab@kuali.org)
051 */
052public class RulesEngineRouteModule implements RouteModule {
053
054    private static final Logger LOG = Logger.getLogger(RulesEngineRouteModule.class);
055
056    private static final String RULES_ENGINE_ELEMENT = "rulesEngine";
057    private static final String EXECUTOR_NAME_ATTRIBUTE = "executorName";
058    private static final String EXECUTOR_CLASS_ATTRIBUTE = "executorClass";
059    private static final String PEOPLE_FLOWS_SELECTED_ATTRIBUTE = "peopleFlowsSelected";
060
061    private volatile Engine rulesEngine;
062
063    private PeopleFlowRouteModule peopleFlowRouteModule;
064    private ExtensionRepositoryService extensionRepositoryService;
065    private RouteNodeService routeNodeService;
066
067    @Override
068    public List<ActionRequestValue> findActionRequests(RouteContext context) throws Exception {
069        EngineResults engineResults = executeRulesEngine(context, getRulesEngine());
070        if (engineResults != null) {
071            processEngineResults(context, engineResults);
072        }
073        return peopleFlowRouteModule.findActionRequests(context);
074    }
075
076    @Override
077    public ResponsibleParty resolveResponsibilityId(String responsibilityId) throws WorkflowException {
078        return null;
079    }
080
081    @Override
082    public boolean isMoreRequestsAvailable(RouteContext context) {
083        return peopleFlowRouteModule.isMoreRequestsAvailable(context);
084    }
085
086    protected EngineResults executeRulesEngine(RouteContext context, Engine rulesEngine) {
087        RulesEngineExecutor executor = loadRulesEngineExecutor(context);
088        return executor.execute(context, rulesEngine);
089    }
090
091    protected void processEngineResults(RouteContext context, EngineResults engineResults) {
092        String peopleFlowsSelected = (String)engineResults.getAttribute(PEOPLE_FLOWS_SELECTED_ATTRIBUTE);
093        if (StringUtils.isBlank(peopleFlowsSelected)) {
094            LOG.info("No PeopleFlows returned from KRMS execution.");
095        } else {
096            LOG.info("PeopleFlows returned from KRMS execution: " + peopleFlowsSelected);
097        }
098        NodeState nodeState = context.getNodeInstance().getNodeState(PeopleFlowRouteModule.PEOPLE_FLOW_SEQUENCE);
099        if (nodeState == null) {
100            nodeState = new NodeState();
101            nodeState.setNodeInstance(context.getNodeInstance());
102            nodeState.setKey(PeopleFlowRouteModule.PEOPLE_FLOW_SEQUENCE);
103            context.getNodeInstance().addNodeState(nodeState);
104        }
105        nodeState.setValue(peopleFlowsSelected);
106        if (!context.isSimulation()) {
107            routeNodeService.save(nodeState);
108        }
109    }
110
111    protected RulesEngineExecutor loadRulesEngineExecutor(RouteContext context) {
112        RouteNode routeNode = context.getNodeInstance().getRouteNode();
113        Element rulesEngineElement = RouteNodeUtils.getCustomRouteNodeElement(
114                context.getNodeInstance().getRouteNode(), RULES_ENGINE_ELEMENT);
115        if (rulesEngineElement == null) {
116            throw new ConfigurationException("Failed to located rules engine configuration for route node: " + routeNode.getName());
117        }
118        String executorName = rulesEngineElement.getAttribute(EXECUTOR_NAME_ATTRIBUTE);
119        String executorClassName = rulesEngineElement.getAttribute(EXECUTOR_CLASS_ATTRIBUTE);
120        if (StringUtils.isBlank(executorName) && StringUtils.isBlank(executorClassName)) {
121            throw new ConfigurationException("Failed to resolve a valid executor name or class name from rules engine configuration, was null or blank.");
122        }
123        RulesEngineExecutor rulesEngineExecutor = null;
124        if (StringUtils.isNotBlank(executorClassName)) {
125            rulesEngineExecutor = GlobalResourceLoader.getObject(new ObjectDefinition(executorClassName));
126        } else if (StringUtils.isNotBlank(executorName)) {
127            ExtensionDefinition extensionDefinition = getExtensionRepositoryService().getExtensionByName(executorName);
128            if (extensionDefinition != null) {
129                rulesEngineExecutor = ExtensionUtils.loadExtension(extensionDefinition);
130            }
131        }
132        if (rulesEngineExecutor == null) {
133            throw new ConfigurationException("Failed to load RulesEngineExecutor for either executorName=" + executorName + " or executorClass=" + executorClassName);
134        }
135        return rulesEngineExecutor;
136    }
137
138    protected Engine getRulesEngine() {
139        if (rulesEngine == null) {
140            rulesEngine = KrmsApiServiceLocator.getEngine();
141        }
142        return rulesEngine;
143    }
144
145    public PeopleFlowRouteModule getPeopleFlowRouteModule() {
146        return peopleFlowRouteModule;
147    }
148
149    public void setPeopleFlowRouteModule(PeopleFlowRouteModule peopleFlowRouteModule) {
150        this.peopleFlowRouteModule = peopleFlowRouteModule;
151    }
152
153    public ExtensionRepositoryService getExtensionRepositoryService() {
154        return extensionRepositoryService;
155    }
156
157    public void setExtensionRepositoryService(ExtensionRepositoryService extensionRepositoryService) {
158        this.extensionRepositoryService = extensionRepositoryService;
159    }
160
161    public RouteNodeService getRouteNodeService() {
162        return routeNodeService;
163    }
164
165    public void setRouteNodeService(RouteNodeService routeNodeService) {
166        this.routeNodeService = routeNodeService;
167    }
168    
169}