/* * Copyright 2017 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jbpm.process.workitem.bpmn2; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; import org.jbpm.process.workitem.AbstractLogOrThrowWorkItemHandler; import org.kie.api.KieServices; import org.kie.api.builder.KieScanner; import org.kie.api.command.BatchExecutionCommand; import org.kie.api.command.Command; import org.kie.api.command.KieCommands; import org.kie.api.runtime.ExecutionResults; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; import org.kie.api.runtime.StatelessKieSession; import org.kie.api.runtime.process.WorkItem; import org.kie.api.runtime.process.WorkItemManager; import org.kie.api.runtime.rule.FactHandle; import org.kie.dmn.api.core.DMNContext; import org.kie.dmn.api.core.DMNModel; import org.kie.dmn.api.core.DMNResult; import org.kie.dmn.api.core.DMNRuntime; import org.kie.dmn.api.core.DMNMessage.Severity; import org.kie.internal.runtime.Cacheable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Additional BusinesRuleTask support that allows to decouple rules from processes - as default BusinessRuleTask * uses exact same working memory (kie session) as process which essentially means same kbase. * To allow better separation and maintainability BusinessRuleTaskHandler is provided that supports: * <ul> * <li>DRL stateful</li> * <li>DRL stateless</li> * <li>DMN</li> * </ul> * Type of runtime is selected by Language data input and if not given defaults to DRL stateless. * * Session type can be given by KieSessionType data input and session name can be given as KieSessionName property -these apply to DRL only. * * DMN support following data inputs: * <ul> * <li>Namespace - DMN namespace to be used - mandatory</li> * <li>Model - DMN model to be used - mandatory</li> * <li>Decision - DMN decision name to be used - optional</li> * </ul> * * Results returned will be then put back into the data outputs. <br/> * <br/> * DRL handling is based on same names for data input and output as that is then used as correlation.<br/> * DMN handling receives all data from DMNResult.<br/> */ public class BusinessRuleTaskHandler extends AbstractLogOrThrowWorkItemHandler implements Cacheable { private static final Logger logger = LoggerFactory.getLogger(BusinessRuleTaskHandler.class); protected static final String STATELESS_TYPE = "stateless"; protected static final String STATEFULL_TYPE = "statefull"; protected static final String DRL_LANG = "DRL"; protected static final String DMN_LANG = "DMN"; private KieServices kieServices = KieServices.get(); private KieCommands commandsFactory = kieServices.getCommands(); private KieContainer kieContainer; private KieScanner kieScanner; public BusinessRuleTaskHandler(String groupId, String artifactId, String version) { this(groupId, artifactId, version, -1); } public BusinessRuleTaskHandler(String groupId, String artifactId, String version, long scannerInterval) { logger.debug("About to create KieContainer for {}, {}, {} with scanner interval {}", groupId, artifactId, version, scannerInterval); kieContainer = kieServices.newKieContainer(kieServices.newReleaseId(groupId, artifactId, version)); if (scannerInterval > 0) { kieScanner = kieServices.newKieScanner(kieContainer); kieScanner.start(scannerInterval); logger.debug("Scanner started for {} with poll interval set to {}", kieContainer, scannerInterval); } } public void executeWorkItem(WorkItem workItem, final WorkItemManager manager) { Map<String, Object> parameters = new HashMap<>(workItem.getParameters()); String language = (String) parameters.remove("Language"); if (language == null) { language = DRL_LANG; } String kieSessionName = (String) parameters.remove("KieSessionName"); String kieSessionType = (String) parameters.remove("KieSessionType"); if (kieSessionType == null) { kieSessionType = STATELESS_TYPE; } Map<String, Object> results = new HashMap<>(); try { logger.debug("Facts to be inserted into working memory {}", parameters); if (DRL_LANG.equalsIgnoreCase(language)) { if (STATEFULL_TYPE.equalsIgnoreCase(kieSessionType)) { handleStatefull(workItem, kieSessionName, parameters, results); } else { handleStateless(workItem, kieSessionName, parameters, results); } } else if (DMN_LANG.equalsIgnoreCase(language)) { handleDMN(workItem, parameters, results); } else { throw new IllegalArgumentException("Not supported language type " + language); } logger.debug("Facts retrieved from working memory {}", results); manager.completeWorkItem(workItem.getId(), results); } catch (Exception e) { handleException(e); } } @Override public void abortWorkItem(WorkItem workItem, WorkItemManager manager) { // no-op } @Override public void close() { if (kieScanner != null) { kieScanner.shutdown(); logger.debug("Scanner shutdown for kie container {}", kieContainer); } kieContainer.dispose(); } protected void handleStatefull(WorkItem workItem, String kieSessionName, Map<String, Object> parameters, Map<String, Object> results) { logger.debug("Evalating rules in statefull session with name {}", kieSessionName); Map<String, FactHandle> factHandles = new HashMap<String, FactHandle>(); KieSession kieSession = kieContainer.newKieSession(kieSessionName); for (Entry<String, Object> entry : parameters.entrySet()) { String inputKey = workItem.getId() + "_" + entry.getKey(); factHandles.put(inputKey, kieSession.insert(entry.getValue())); } int fired = kieSession.fireAllRules(); logger.debug("{} rules fired", fired); for (Entry<String, FactHandle> entry : factHandles.entrySet()) { Object object = kieSession.getObject(entry.getValue()); String key = entry.getKey().replaceAll(workItem.getId() + "_", ""); results.put(key , object); kieSession.delete(entry.getValue()); } factHandles.clear(); } protected void handleStateless(WorkItem workItem, String kieSessionName, Map<String, Object> parameters, Map<String, Object> results) { logger.debug("Evalating rules in stateless session with name {}", kieSessionName); StatelessKieSession kieSession = kieContainer.newStatelessKieSession(kieSessionName); List<Command<?>> commands = new ArrayList<Command<?>>(); for (Entry<String, Object> entry : parameters.entrySet()) { String inputKey = workItem.getId() + "_" + entry.getKey(); commands.add(commandsFactory.newInsert(entry.getValue(), inputKey, true, null)); } commands.add(commandsFactory.newFireAllRules("Fired")); BatchExecutionCommand executionCommand = commandsFactory.newBatchExecution(commands); ExecutionResults executionResults = kieSession.execute(executionCommand); logger.debug("{} rules fired", executionResults.getValue("Fired")); for (Entry<String, Object> entry : parameters.entrySet()) { String inputKey = workItem.getId() + "_" + entry.getKey(); String key = entry.getKey().replaceAll(workItem.getId() + "_", ""); results.put(key, executionResults.getValue(inputKey)); } } protected void handleDMN(WorkItem workItem, Map<String, Object> parameters, Map<String, Object> results) { String namespace = (String) parameters.remove("Namespace"); String model = (String) parameters.remove("Model"); String decision = (String) parameters.remove("Decision"); DMNRuntime runtime = kieContainer.newKieSession().getKieRuntime(DMNRuntime.class); DMNModel dmnModel = runtime.getModel(namespace, model); if (dmnModel == null) { throw new IllegalArgumentException("DMN model '" + model + "' not found with namespace '" + namespace + "'"); } DMNResult dmnResult = null; DMNContext context = runtime.newContext(); for (Entry<String, Object> entry : parameters.entrySet()) { context.set(entry.getKey(), entry.getValue()); } if (decision != null && !decision.isEmpty()) { dmnResult = runtime.evaluateDecisionByName(dmnModel, decision, context); } else { dmnResult = runtime.evaluateAll(dmnModel, context); } if (dmnResult.hasErrors()) { String errors = dmnResult.getMessages(Severity.ERROR).stream() .map(message -> message.toString()) .collect(Collectors.joining(", ")); throw new RuntimeException("DMN result errors:: " + errors); } results.putAll(dmnResult.getContext().getAll()); } public KieContainer getKieContainer() { return this.kieContainer; } }