/**
* Copyright (C) 2010-2012 BonitaSoft S.A.
* BonitaSoft, 32 rue Gustave Eiffel - 38000 Grenoble
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2.0 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.bonitasoft.simulation.engine;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Properties;
import java.util.Set;
import org.bonitasoft.simulation.iteration.IterationDescriptor;
import org.bonitasoft.simulation.iteration.IterationDetection;
import org.bonitasoft.simulation.iteration.IterationNode;
import org.bonitasoft.simulation.iteration.IterationNode.SplitType;
import org.bonitasoft.simulation.iteration.IterationProcess;
import org.bonitasoft.simulation.iteration.IterationTransition;
import org.bonitasoft.simulation.model.Period;
import org.bonitasoft.simulation.model.RepartitionType;
import org.bonitasoft.simulation.model.calendar.SimCalendar;
import org.bonitasoft.simulation.model.instance.RuntimeTask;
import org.bonitasoft.simulation.model.instance.SimActivityInstance;
import org.bonitasoft.simulation.model.instance.SimProcessInstance;
import org.bonitasoft.simulation.model.loadprofile.InjectionPeriod;
import org.bonitasoft.simulation.model.loadprofile.LoadProfile;
import org.bonitasoft.simulation.model.process.JoinType;
import org.bonitasoft.simulation.model.process.SimActivity;
import org.bonitasoft.simulation.model.process.SimProcess;
import org.bonitasoft.simulation.model.process.SimTransition;
import org.bonitasoft.simulation.model.resource.Resource;
import org.bonitasoft.simulation.reporting.CSVSimReportStorage;
import org.bonitasoft.simulation.reporting.ISimulationStore;
import org.bonitasoft.simulation.reporting.SimReport;
import org.bonitasoft.simulation.reporting.SimReportFactory;
/**
* @author Romain Bioteau
*
*/
public class SimulationEngine {
public final static String MAXIMUM_DELAY_ACTIVITY = "maxDelayProperty"; //$NON-NLS-1$
public final static String REPORT_WORKSPACE = "reportWorkspace"; //$NON-NLS-1$
public final static String REPORT_TIMESPAN = "timespan"; //$NON-NLS-1$
public static final String FLUSH_STORE = "flushStore"; //$NON-NLS-1$
public static final String EXPORT_MODE = "exportMode";
public static final String HTML_MODE = "HTML";
public static final String PDF_MODE = "PDF";
public static final String EXCEL_MODE = "EXCEL";
public static long currentTime = 0;
private static final int INSTANCE_BUFFER_SIZE = 100 ;
public static boolean isStopped = false ;
private int nbOfLoopWhileUnavailable = 0 ;
private PriorityQueue<RuntimeTask> todoList;
private LoadProfile loadProfile;
private SimProcess simulationProcess;
private ISimulationStore store ;
private long simulationStartDate ;
private long simulationEndDate ;
private String workspace;
private long timespan;
private List<Resource> inputResources;
private long nextInterval;
private int executedInstance = 0;
private long executionStart;
private boolean flushStore = true ;
private String reportFile;
private boolean isGeneratingReport = false;
private int totalInstances = 0;
public SimulationEngine(SimProcess simulationProcess, LoadProfile loadProfile,List<Resource> inputResources,Properties executionProperties) throws IOException{
currentTime = 0 ;
isStopped = false ;
this.simulationProcess = simulationProcess ;
this.loadProfile = loadProfile ;
this.inputResources = inputResources ;
if(executionProperties != null && executionProperties.get(MAXIMUM_DELAY_ACTIVITY) != null){
nbOfLoopWhileUnavailable = (Integer)executionProperties.get(MAXIMUM_DELAY_ACTIVITY) ;
}
if(executionProperties != null && executionProperties.get(REPORT_WORKSPACE) != null){
this.workspace = (String)executionProperties.get(REPORT_WORKSPACE) ;
if(executionProperties != null && executionProperties.get(FLUSH_STORE) != null){
this.flushStore = (Boolean) executionProperties.get(FLUSH_STORE) ;
}
this.store = new CSVSimReportStorage(workspace,simulationProcess.getName(),flushStore) ;
}
if(executionProperties != null && executionProperties.get(REPORT_TIMESPAN) != null){
this.timespan = (Long) executionProperties.get(REPORT_TIMESPAN) ;
}
if(timespan == 0){
timespan = (long) loadProfile.getTotalDuration() / (long) 12 ;
}
DefinitionPool.createInstance() ;
DataUtil.createInstance() ;
}
public void start() throws Exception {
executionStart = System.currentTimeMillis() ;
System.err.println("Detecting Cycles"); //$NON-NLS-1$
detectCycles();
System.err.println(simulationProcess.getCycles().size()+" Cycles found"); //$NON-NLS-1$
System.err.println("Create TodoList"); //$NON-NLS-1$
createTodoList() ;//CREATE INJECTION CALENDAR
this.todoList = new PriorityQueue<RuntimeTask>();
initTodoList();
simulationStartDate = this.todoList.peek().getStartDate() ;
currentTime = simulationStartDate ;
ResourcePool.getInstance().createResources(simulationProcess,inputResources, currentTime,timespan);
long nextInjection = ((RuntimeTask) todoList.toArray()[todoList.size()-1] ).getStartDate();
nextInterval = simulationStartDate + timespan ;
System.err.println("Started"); //$NON-NLS-1$
long previous = System.currentTimeMillis();
while(!isStopped && !todoList.isEmpty()){
RuntimeTask taskTodo = todoList.poll() ;
currentTime = taskTodo.getStartDate() ;
nextInjection = loadTodoListFromStoreAndRetrieveNextInjection(nextInjection);
previous = updateResourceCount(previous);
if(taskTodo.getTask().getStartDate() == 0){
taskTodo.getTask().setStartDate(currentTime);
}
SimActivity activity = (SimActivity) taskTodo.getTask().getDefinition() ;
final boolean waiting = isWaitingForAnotherActivity(taskTodo, activity);
if(!waiting){
if (!activity.hasResources()) {
if(activity.getJoinType().equals(JoinType.XOR)){
taskTodo.getTask().addIncoming();
if(taskTodo.getTask().getIncomings() == 1){//SKIP THE EXECUTION
executeActivityInstances(currentTime, taskTodo,null);
}
}else{
executeActivityInstances(currentTime, taskTodo,null);
}
} else {
executeActivityInstanceIfResourceAvailable(taskTodo, activity);
}
}
}
ResourcePool.getInstance().updateResourceConsumption(getSimulationEndDate()) ;
if(!isStopped){
generateReport();
}
currentTime = 0 ;
getStore().closeStore();
}
protected long updateResourceCount(long previous) {
if(currentTime >= nextInterval){//UPDATE RESOURCE COUNT
System.err.println("Process instance finished = " + executedInstance + ", executed in " + (System.currentTimeMillis() - previous)); //$NON-NLS-1$ //$NON-NLS-2$
executedInstance = 0 ;
previous = System.currentTimeMillis();
ResourcePool.getInstance().updateResourceConsumption(nextInterval) ;
nextInterval = nextInterval + timespan ;
}
return previous;
}
protected void executeActivityInstanceIfResourceAvailable(
RuntimeTask taskTodo, SimActivity activity) throws Exception {
final Set<ResourceInstanceAvailability> availableResources = ResourcePool.getInstance().getNextAvailableDateForAllResources(taskTodo.getTask().getProcessInstance().getInstanceUUID(),activity.getName(),currentTime, activity.getAssignedResources(), activity.isContigous());
final long nextAvailableDate = availableResources.iterator().next().getTime();
if ( nextAvailableDate == currentTime || nbOfLoopWhileUnavailable <= taskTodo.getTask().getSkip() ) {
if(activity.getJoinType().equals(JoinType.XOR)){
taskTodo.getTask().addIncoming();
if(taskTodo.getTask().getIncomings() == 1){//SKIP THE EXECUTION
executeActivityInstances(nextAvailableDate, taskTodo,availableResources);
}
}else{
executeActivityInstances(nextAvailableDate, taskTodo,availableResources);
}
} else {
taskTodo.getTask().skip() ;
taskTodo.setStartDate(nextAvailableDate);
todoList.offer(taskTodo);
}
}
protected void generateReport() throws Exception {
isGeneratingReport = true ;
SimReport report = new SimReportFactory(workspace,getLoadProfile(), getStore(),ResourcePool.getInstance().getResourceInstances(), getSimulationStartDate(), getSimulationEndDate(), timespan,executionStart).createSimulationReport() ;
reportFile = report.generate() ;
}
protected boolean isWaitingForAnotherActivity(RuntimeTask taskTodo,
SimActivity activity) {
boolean waiting = false ;
if(activity.getJoinType().equals(JoinType.AND)){
if(taskTodo.getTask().getIncomings() != activity.getIncomingTransitions().size()){//WAIT FOR THE OTHERS ACTIVTY
waiting = true ;
taskTodo.setStartDate(Long.MAX_VALUE) ;
todoList.offer(taskTodo);
}
}else if(activity.getJoinType().equals(JoinType.XOR)){
if(taskTodo.getTask().getIncomings() > 1){//SKIP THE EXECUTION
waiting = true ;
}
}
return waiting;
}
protected void initTodoList() throws Exception {
List<SimProcessInstance> instances = store.getStoredProcessInstances(INSTANCE_BUFFER_SIZE) ;
for(SimProcessInstance instance : instances){
for(SimActivityInstance startActivity : instance.getStartElemInstances()){
todoList.add(new RuntimeTask(startActivity,instance.getStartDate()));
}
}
}
protected long loadTodoListFromStoreAndRetrieveNextInjection(long nextInjection) throws Exception {
List<SimProcessInstance> instances;
if(currentTime >= nextInjection){//LOAD TODOLIST FROM STORE
instances = store.getStoredProcessInstances(INSTANCE_BUFFER_SIZE) ;
if(!instances.isEmpty()){
for(SimProcessInstance instance : instances){
for(SimActivityInstance startActivity : instance.getStartElemInstances()){
todoList.offer(new RuntimeTask(startActivity,instance.getStartDate()));
}
}
nextInjection = instances.get(instances.size()-1).getStartDate();
}else{
nextInjection = Long.MAX_VALUE ;
}
}
return nextInjection;
}
private void detectCycles() {
final IterationProcess iterationProcess = new IterationProcess();
for (SimActivity activityDefinition : simulationProcess.getActivities()) {
final String joinType = activityDefinition.getJoinType().toString();
final IterationNode node = new IterationNode(activityDefinition.getName(),
org.bonitasoft.simulation.iteration.IterationNode.JoinType.valueOf(joinType),
SplitType.XOR);
iterationProcess.addNode(node);
}
for (SimActivity activityDefinition : simulationProcess.getActivities()) {
final IterationNode node = iterationProcess.getNode(activityDefinition.getName());
for (SimTransition transition : activityDefinition.getIncomingTransitions()) {
final IterationNode source = iterationProcess.getNode(transition.getSource().getName());
node.addIncomingTransition(new IterationTransition(source, node));
}
for (SimTransition transition : activityDefinition.getOutgoingTransitions()) {
final IterationNode destination = iterationProcess.getNode(transition.getTarget().getName());
node.addOutgoingTransition(new IterationTransition(node, destination));
}
}
Set<IterationDescriptor> iterationDescriptors = IterationDetection.findIterations(iterationProcess);
// update process
for (final IterationDescriptor iterationDescriptor : iterationDescriptors) {
simulationProcess.addCycle(iterationDescriptor);
for (String activityName : iterationDescriptor.getCycleNodes()) {
final SimActivity activity = simulationProcess.getActivity(activityName);
activity.setInCycle(true);
}
for (String activityName : iterationDescriptor.getEntryNodes()) {
final SimActivity activity = simulationProcess.getActivity(activityName);
activity.setEntryNode(true);
}
for (String activityName : iterationDescriptor.getExitNodes()) {
final SimActivity activity = simulationProcess.getActivity(activityName);
activity.setExitNode(true);
}
}
}
public synchronized void stop(){
isStopped = true ;
}
public void executeActivityInstances(long currentDate,RuntimeTask taskTodo,Set<ResourceInstanceAvailability> resourceInstances) throws Exception {
boolean skipExecution = false ;
SimActivity activity = (SimActivity) taskTodo.getTask().getDefinition() ;
long end = 0 ;
if(resourceInstances != null){
for(ResourceInstanceAvailability r : resourceInstances){
Period p = new Period(r.getTime(), r.getTime() + r.getDuration()) ;
List<Period> periods = r.getResource().getPlanning().split(p) ;
long inAddition = activity.getExecutionTime() - r.getDuration() ;
if(!periods.isEmpty()){
Period latestPeriod = Collections.min(periods);
if(end < (latestPeriod.getEnd()+inAddition)){
end = latestPeriod.getEnd() + inAddition ;
}
}
}
}
if(end == 0){
end = currentDate + activity.getExecutionTime();
}
List<SimActivityInstance> availableActivities = getNextActivities(taskTodo.getTask());
//ADD NEXT ACTIVITIES TO TODOLIST
for(SimActivityInstance nextActivityInstance : availableActivities){
RuntimeTask toRemove = null ;
boolean founded = false ;
if(nextActivityInstance.getExecutionDate() != 0){
skipExecution = true ;
}
if(!skipExecution){
for(RuntimeTask t : todoList){
if(t.getTask().equals(nextActivityInstance)){
founded = true ;
toRemove = t ;
break;
}
}
}
if(founded){
if(((SimActivity) nextActivityInstance.getDefinition()).getJoinType().equals(JoinType.XOR) && nextActivityInstance.getStartDate() <= end ){
skipExecution = true ;
}else{
todoList.remove(toRemove);
RuntimeTask newTask = new RuntimeTask(nextActivityInstance, toRemove.getStartDate()) ;
toRemove = null ;
if(((SimActivity) newTask.getTask().getDefinition()).getJoinType().equals(JoinType.AND)){//KEEP THE LATEST
if(newTask.getStartDate() == Long.MAX_VALUE || newTask.getStartDate() < end){
newTask.setStartDate(end) ;
}
newTask.getTask().addIncoming();
}else{
if(newTask.getStartDate() > end){
newTask.setStartDate(end) ;
}
}
todoList.offer(newTask);
}
}else{
if(((SimActivity) nextActivityInstance.getDefinition()).getJoinType().equals(JoinType.AND)){
nextActivityInstance.addIncoming();
}
todoList.offer(new RuntimeTask(nextActivityInstance, end)) ;
}
taskTodo.getTask().addNext(nextActivityInstance) ;
}
if(!skipExecution){
taskTodo.getTask().setExecutionDate(currentDate);
taskTodo.getTask().setFinishDate(end) ;
if(resourceInstances != null && !resourceInstances.isEmpty()){
ResourcePool.getInstance().lockAvailableResourceInstances(taskTodo.getTask(), activity.getAssignedResources(), activity.isContigous(), resourceInstances); //LOCK INSTANCES
}
DataUtil.getInstance().evaluateActivityData((SimActivityInstance) taskTodo.getTask()); //EVALUATE DATA
if(availableActivities.isEmpty()){
executedInstance ++ ;
taskTodo.getTask().getProcessInstance().setEndDate(end);
List<RuntimeTask> toRemove = new ArrayList<RuntimeTask>() ;
for(RuntimeTask t : todoList){
if(t.getTask().getProcessInstance().equals(taskTodo.getTask().getProcessInstance())){
if(t.getTask().getExecutionDate() == 0){
toRemove.add(t);
}
}
}
if(!toRemove.isEmpty()){
todoList.removeAll(toRemove) ;
}
totalInstances ++ ;
for(String res : ResourcePool.getInstance().getResourceInstances().keySet()){
ResourcePool.getInstance().getResourceInstances().get(res).updateProcessInstanceWaitingFor(taskTodo.getTask().getProcessInstance().getInstanceUUID()) ;
}
store.storeFinishedProcessInstance(taskTodo.getTask().getProcessInstance()) ;
}
taskTodo = null ;
simulationEndDate = end;
}
}
/**
* Return the next activity to perform who potential takes time
* e.g. : Task and Intermediate Event
* @param taskTodo
* @return the activity to perform
* @throws Exception
*/
public List<SimActivityInstance> getNextActivities(SimActivityInstance elem) throws Exception {
final List<SimActivityInstance> results = new ArrayList<SimActivityInstance>();
final Set<SimTransition> outgoingTransitions = ((SimActivity) elem.getDefinition()).getOutgoingTransitions();
final SimTransition[] transitions = (SimTransition[]) outgoingTransitions.toArray(new SimTransition[outgoingTransitions.size()]) ;
if(transitions.length != 0 && !(transitions[0].isDataBased())){//Proba-based Transitions
if(((SimActivity) elem.getDefinition()).isExclusiveOutgoingTransition()){
//EXCLUSIVE TRANSITIONS PROBABILITY
List<List<Integer>> probabilities = new ArrayList<List<Integer>>();
for(int i = 0 ; i<transitions.length ; i++){
final int initialValue = (int) (i > 0 ? transitions[i-1].getProbability()*100 : 0);
final double lastValue = i > 0 ? probabilities.get(i-1).size()+transitions[i].getProbability()*100 : transitions[i].getProbability()*100;
List<Integer> p = new ArrayList<Integer>((int)lastValue - initialValue + 1);
for(int j = initialValue ; j < lastValue; j++){
p.add(j) ;
}
probabilities.add(p);
}
int r = DataUtil.getInstance().getRandom().nextInt(100) ;
for(int i = 0 ; i< probabilities.size() ; i++ ){
if(probabilities.get(i).contains(r)){
results.add(SimulationHelper.getActivityInstance(elem.getProcessInstance(),transitions[i].getTarget(),elem.getIterationId()));
}
}
if(results.isEmpty()){
results.add(SimulationHelper.getActivityInstance(elem.getProcessInstance(),transitions[transitions.length-1].getTarget(),elem.getIterationId())) ;
}
}else{
//UNEXCLUSIVE TRANSITIONS PROBABILITY
for(SimTransition t : transitions){
boolean choice = (DataUtil.getInstance().getRandom().nextInt(100)<t.getProbability()*100) ? true : false;
if(choice){
results.add(SimulationHelper.getActivityInstance(elem.getProcessInstance(), t.getTarget(),elem.getIterationId()));
}
}
}
}else if(transitions.length != 0 && transitions[0].isDataBased()){
for(SimTransition t : transitions){
if(DataUtil.getInstance().evaluateTransition(elem.getProcessInstance(),t)){
results.add(SimulationHelper.getActivityInstance(elem.getProcessInstance(),t.getTarget(),elem.getIterationId()));
}
}
}
return results;
}
/**
* Create the todo task list, this list is an sorted list with the first element representing the first task todo
* @throws Exception
*/
public void createTodoList() throws Exception{
LoadProfile lp = getLoadProfile() ;
SimCalendar offCalendar = lp.getInjectionCalendar() ;
for(InjectionPeriod p : lp.getInjectionPeriods()){
int nbToInject = p.getNumberOfInstance() ;
RepartitionType type = p.getRepartition() ;
long start = p.getPeriod().getBegin() ;
long end = p.getPeriod().getEnd() ;
if(RepartitionType.CONSTANT.equals(type)){
long workingDuration = lp.getInjectionCalendar().getWorkingPlanningDuration(start,end) ;
long interval = workingDuration / nbToInject ;
int injected = 0 ;
long current = 0 ;
long lastInjectionTime = start ;
while(!isStopped && injected < nbToInject && workingDuration > 0){
long injectionTime = offCalendar.getWorkingDurationEndDate(lastInjectionTime,interval) ;
SimProcessInstance instance = SimulationHelper.createInstance(getSimulationProcess(),injectionTime);
for(SimActivity startActivity : ((SimProcess) instance.getDefinition()).getStartElements()){
SimActivityInstance activityInstance = SimulationHelper.getActivityInstance(instance, startActivity,0) ;
instance.addStartElement(activityInstance);
}
store.storeProcessInstance(instance);
injected++ ;
lastInjectionTime = injectionTime ;
current = current + interval ;
workingDuration = workingDuration - interval ;
}
}else if(type.equals(RepartitionType.DIRECT)){
int injected = 0 ;
long injectionTime = offCalendar.getNextPlanningAvailable(start) ;
while(!isStopped && injected < nbToInject){
SimProcessInstance instance = SimulationHelper.createInstance(getSimulationProcess(),injectionTime);
for(SimActivity startActivity : ((SimProcess) instance.getDefinition()).getStartElements()){
SimActivityInstance activity = SimulationHelper.getActivityInstance(instance, startActivity,0) ;
instance.addStartElement(activity);
}
store.storeProcessInstance(instance);
injected++ ;
}
}
}
}
public LoadProfile getLoadProfile() {
return loadProfile;
}
public SimProcess getSimulationProcess(){
return simulationProcess ;
}
public PriorityQueue<RuntimeTask> getTodoList(){
return todoList ;
}
public void setTodoList(PriorityQueue<RuntimeTask> todoList){
this.todoList = todoList ;
}
public ISimulationStore getStore() {
return store ;
}
public long getSimulationStartDate() {
return simulationStartDate;
}
public long getSimulationEndDate() {
return simulationEndDate;
}
public String getReportFile() {
return reportFile;
}
public boolean isStopped(){
return isStopped ;
}
public boolean isGeneratingReport(){
return isGeneratingReport ;
}
public int getTotalInstances() {
return totalInstances;
}
}