/*
* Copyright 2015 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.simulation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.drools.core.command.runtime.DisposeCommand;
import org.drools.core.fluent.impl.BaseBatchFluent;
import org.drools.core.fluent.impl.PseudoClockRunner;
import org.drools.core.time.SessionPseudoClock;
import org.jbpm.process.core.validation.ProcessValidatorRegistry;
import org.jbpm.simulation.converter.SimulationFilterPathFormatConverter;
import org.jbpm.simulation.impl.BPMN2SimulationDataProvider;
import org.jbpm.simulation.impl.SimulateProcessPathCommand;
import org.jbpm.simulation.impl.SimulationPath;
import org.jbpm.simulation.impl.SimulationProcessValidator;
import org.jbpm.simulation.impl.WorkingMemorySimulationRepository;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieModule;
import org.kie.api.builder.Message;
import org.kie.api.builder.ReleaseId;
import org.kie.api.builder.model.KieBaseModel;
import org.kie.api.builder.model.KieModuleModel;
import org.kie.api.builder.model.KieSessionModel;
import org.kie.api.conf.EqualityBehaviorOption;
import org.kie.api.conf.EventProcessingOption;
import org.kie.api.io.Resource;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.builder.ExecutableBuilder;
import org.kie.api.runtime.builder.KieSessionFluent;
import org.kie.api.runtime.conf.ClockTypeOption;
import org.kie.internal.io.ResourceFactory;
public class SimulationRunner {
static {
ProcessValidatorRegistry.getInstance().registerAdditonalValidator(new SimulationProcessValidator());
}
public static SimulationRepository runSimulation(String processId, String bpmn2Container, int numberOfAllInstances, long interval, String... rules) {
return runSimulation(processId, bpmn2Container, numberOfAllInstances, interval, false, rules);
}
public static SimulationRepository runSimulation(String processId, String bpmn2Container, int numberOfAllInstances, long interval, boolean runRules, String... rules) {
Resource[] resources = new Resource[rules.length];
for (int i = 0; i < rules.length; i++) {
resources[i] = ResourceFactory.newClassPathResource(rules[i]);
}
return runSimulation(processId, bpmn2Container, numberOfAllInstances, interval, runRules, resources);
}
public static SimulationRepository runSimulation(String processId, String bpmn2Container, int numberOfAllInstances, long interval, boolean runRules, Resource... rules) {
SimulationContext context = SimulationContextFactory.newContext(new BPMN2SimulationDataProvider(bpmn2Container), new WorkingMemorySimulationRepository(runRules, rules));
SimulationDataProvider provider = context.getDataProvider();
PathFinder finder = PathFinderFactory.getInstance(bpmn2Container);
List<SimulationPath> paths = finder.findPaths(new SimulationFilterPathFormatConverter(provider));
// TODO when introduced configurable start time that should be used instead of currentTimeMillis
context.getRepository().setSimulationInfo(new SimulationInfo(System.currentTimeMillis(), processId, numberOfAllInstances, interval));
final ReleaseId releaseId = createKJarWithMultipleResources(processId,
new String[]{bpmn2Container}, new ResourceType[]{ResourceType.BPMN2});
PseudoClockRunner runner = new PseudoClockRunner();
ExecutableBuilder f = ExecutableBuilder.create();
List<Long> startTimes = generateStartTimes(interval, numberOfAllInstances);
int startIndex = 0;
// @formatter:off
int counter = 0;
int remainingInstances = numberOfAllInstances;
for (SimulationPath path : paths) {
// only paths that can be started are considered
if (!path.isStartable()) {
continue;
}
double probability = path.getProbability();
f.newApplicationContext("path" + counter);
int instancesOfPath = 1;
// count how many instances/steps should current path have
if (numberOfAllInstances > 1) {
instancesOfPath = (int) Math.round((numberOfAllInstances * probability));
// ensure that we won't exceed total number of instance due to rounding
if (instancesOfPath > remainingInstances) {
instancesOfPath = remainingInstances;
}
List<Long> pathStartTimes = startTimes.subList(startIndex, startIndex + instancesOfPath);
Collections.sort(pathStartTimes);
startIndex = pathStartTimes.size();
remainingInstances -= instancesOfPath;
for (int i = 0; i < instancesOfPath; i++) {
KieSessionFluent sessionFluent = f.after(pathStartTimes.get(i))
.getKieContainer(releaseId)
.newSession();
((BaseBatchFluent) sessionFluent).addCommand(new SimulateProcessPathCommand(processId, context, path));
// ((BaseBatchFluent) sessionFluent).addCommand(new SetVariableCommandFromLastReturn(StatefulKnowledgeSession.class.getName()));
((BaseBatchFluent) sessionFluent).addCommand(new DisposeCommand());
}
} else {
KieSessionFluent sessionFluent = f.after(interval)
.getKieContainer(releaseId)
.newSession();
((BaseBatchFluent) sessionFluent).addCommand(new SimulateProcessPathCommand(processId, context, path));
// ((BaseBatchFluent) sessionFluent).addCommand(new SetVariableCommandFromLastReturn(StatefulKnowledgeSession.class.getName()));
((BaseBatchFluent) sessionFluent).addCommand(new DisposeCommand());
break;
}
counter++;
// currently standalone paths within single definition are not supported
// if (probability == 1) {
// // in case given path has probability of 100% there is a need to reset the remaining instances
// // as this is standalone process path
// remainingInstances = numberOfAllInstances;
// }
}
runner.execute(f.getExecutable());
// @formatter:on
context.getRepository().getSimulationInfo().setEndTime(context.getMaxEndTime());
return context.getRepository();
}
protected static ReleaseId createKJarWithMultipleResources(String id, String[] resources, ResourceType[] types) {
KieServices ks = KieServices.Factory.get();
KieModuleModel kproj = ks.newKieModuleModel();
KieFileSystem kfs = ks.newKieFileSystem();
kfs.writePomXML(getPom("org.jbpm.sim", id, "1.0"));
for (int i = 0; i < resources.length; i++) {
String res = resources[i];
String type = types[i].getDefaultExtension();
kfs.write("src/main/resources/" + id.replaceAll("\\.", "/")
+ "/org/test/res" + i + ".bpsim." + type, res);
}
KieBaseModel kBase1 = kproj.newKieBaseModel(id)
.setEqualsBehavior(EqualityBehaviorOption.EQUALITY)
.setEventProcessingMode(EventProcessingOption.STREAM)
.setDefault(true);
KieSessionModel ksession1 = kBase1
.newKieSessionModel(id + ".KSession1")
.setType(KieSessionModel.KieSessionType.STATEFUL)
.setClockType(ClockTypeOption.get("pseudo"))
.setDefault(true);
kfs.writeKModuleXML(kproj.toXML());
KieBuilder kieBuilder = ks.newKieBuilder(kfs).buildAll();
if(!kieBuilder.getResults().getMessages().isEmpty()) {
for (Message msg : kieBuilder.getResults().getMessages()) {
System.out.println("[ERROR]" + msg.getText());
}
throw new RuntimeException("Error building knowledge base, see previous errors");
}
KieModule kieModule = kieBuilder.getKieModule();
return kieModule.getReleaseId();
}
protected static String getPom(String groupId, String artifactId, String version) {
String pom =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<project xmlns=\"http://maven.apache.org/POM/4.0.0\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n" +
" xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">\n" +
" <modelVersion>4.0.0</modelVersion>\n" +
"\n" +
" <groupId>" + groupId + "</groupId>\n" +
" <artifactId>" + artifactId + "</artifactId>\n" +
" <version>" + version + "</version>\n" +
"\n";
pom += "</project>";
return pom;
}
protected static List<Long> generateStartTimes(long interval, int numberOfInstances) {
List<Long> startTimes = new ArrayList<Long>();
for (int i = 0; i < numberOfInstances; i++) {
startTimes.add(interval * i);
}
Collections.shuffle(startTimes);
return startTimes;
}
}