package sushi.monitoring.bpmn; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import sushi.bpmn.decomposition.Component; import sushi.bpmn.element.AbstractBPMNElement; import sushi.bpmn.element.BPMNEndEvent; import sushi.event.collection.SushiTree; import sushi.query.PatternQueryType; import sushi.query.SushiPatternQuery; /** * The ViolationMonitor tries to reveal violations while the execution of process instances. * For instance order, exclusiveness or cooccurence violations. * @author micha */ public class ViolationMonitor { private ProcessInstanceMonitor processInstanceMonitor; private SushiTree<AbstractBPMNElement> processDecompositionTree; /** * Creates a new ViolationMonitor for the given {@link ProcessInstanceMonitor} to monitor execution violations. * @param processInstanceMonitor */ public ViolationMonitor(ProcessInstanceMonitor processInstanceMonitor) { this.processInstanceMonitor = processInstanceMonitor; this.processDecompositionTree = this.processInstanceMonitor.getProcessInstance().getProcess().getProcessDecompositionTree(); } /** * Searches for order, exclusiveness and cooccurence violations, during the execution of the specified process instance. */ public void searchForViolations(){ searchForOrderViolations(); searchForExclusivenessViolations(); searchForOccurenceViolations(); searchForLoopViolations(); } /** * This method searches for order violations in sequential components. So if the elements in the component are triggered * in the false order, the sequential components has an order violation */ private void searchForOrderViolations() { //TODO: OrderViolation: Elemente in einer Sequenz, Reihenfolge der Elemente ermitteln, falls Elemente alle getriggert, aber in falscher Reihenfolge for(QueryMonitor queryMonitor : processInstanceMonitor.getQueryMonitorsWithQueryType(PatternQueryType.SEQUENCE)){ if(queryMonitor.isRunning()){ List<QueryMonitor> subQueryMonitors = processInstanceMonitor.getSubQueryMonitors(queryMonitor); if(!subQueryMonitors.contains(null) && subQueryMonitors.size() > 1){ subQueryMonitors = orderQueryMonitorsSequential(queryMonitor); boolean allSubQueriesTerminated = true; boolean timeViolation = false; int queryExecution = subQueryMonitors.get(0).getExecutionCount(); Date queryEndTime = subQueryMonitors.get(0).getEndTime(); for(QueryMonitor subQueryMonitor : subQueryMonitors){ //TODO: Sollte Status terminate gefragt werden, also auch skipped oder nur finished? //Alle terminated und gleiche Anzahl von Executions if(!subQueryMonitor.isTerminated() || subQueryMonitor.getExecutionCount() != queryExecution){ allSubQueriesTerminated = false; break; } if(queryEndTime.after(subQueryMonitor.getEndTime())){ timeViolation = true; } queryEndTime = subQueryMonitor.getEndTime(); } //Alle Subqueries sind durchgelaufen if(allSubQueriesTerminated && timeViolation){ queryMonitor.addViolationStatus(ViolationStatus.Order); queryMonitor.setQueryStatus(QueryStatus.Finished); //TODO: sollte adaptQueries hier nochmal gerufen werden? } } } } } /** * Tries to order {@link QueryMonitor}s which belong to a sequence. * @param sequentialQueryMonitors * @return */ private List<QueryMonitor> orderQueryMonitorsSequential(QueryMonitor sequentialQueryMonitor) { List<QueryMonitor> subQueryMonitors = processInstanceMonitor.getSubQueryMonitors(sequentialQueryMonitor); List<AbstractBPMNElement> sequentialParents = processDecompositionTree.getParents(sequentialQueryMonitor.getQuery().getMonitoredElements()); if(sequentialParents.size() == 1 && sequentialParents.get(0) instanceof Component){ List<QueryMonitor> orderedQueryMonitors = new ArrayList<QueryMonitor>(); Component sequentialComponent = (Component) sequentialParents.get(0); //Hier sollte jeweils nur ein Element zurückkommen orderedQueryMonitors.addAll(processInstanceMonitor.getQueryMonitorsWithMonitoredElements(Arrays.asList(sequentialComponent.getSourceElement()))); orderedQueryMonitors.addAll(processInstanceMonitor.getQueryMonitorsWithMonitoredElements(Arrays.asList(sequentialComponent.getSinkElement()))); for(QueryMonitor queryMonitor : subQueryMonitors){ if(!orderedQueryMonitors.contains(queryMonitor)){ QueryMonitor orderQueryMonitor; if(searchPredecessor(queryMonitor, orderedQueryMonitors) != null){ orderQueryMonitor = searchPredecessor(queryMonitor, orderedQueryMonitors); orderedQueryMonitors.add(orderedQueryMonitors.indexOf(orderQueryMonitor) + 1, queryMonitor); } else if(searchSuccessor(queryMonitor, orderedQueryMonitors) != null){ orderQueryMonitor = searchSuccessor(queryMonitor, orderedQueryMonitors); orderedQueryMonitors.add(orderedQueryMonitors.indexOf(orderQueryMonitor), queryMonitor); } else { orderedQueryMonitors.add(queryMonitor); } } } return orderedQueryMonitors; } else { System.err.println("Elements could not be ordered!"); return subQueryMonitors; } } /** * Searches for an QueryMonitor from the list of orderedQueryMonitors, that could be the predecessor of the specified QueryMonitor. * @param queryMonitor * @param orderedQueryMonitors * @return */ private QueryMonitor searchPredecessor(QueryMonitor queryMonitor, List<QueryMonitor> orderedQueryMonitors) { Set<AbstractBPMNElement> predecessors; for(QueryMonitor orderedQueryMonitor : orderedQueryMonitors){ predecessors = new HashSet<AbstractBPMNElement>(); if(queryMonitor.getQuery().getPatternQueryType().equals(PatternQueryType.STATETRANSITION)){ for(AbstractBPMNElement monitoredElement : queryMonitor.getQuery().getMonitoredElements()){ predecessors.addAll(monitoredElement.getPredecessors()); } //Wenn es keine StateTransition ist, beobacht die Query eine Component, also bekommt man den Vorgänger als EntryPoint der Component }else{ for(AbstractBPMNElement parent : processDecompositionTree.getParents(queryMonitor.getQuery().getMonitoredElements())){ if(parent instanceof Component){ Component parentComponent = (Component) parent; predecessors.add(parentComponent.getEntryPoint()); } } } predecessors.retainAll(orderedQueryMonitor.getQuery().getMonitoredElements()); if(!predecessors.isEmpty()){ return orderedQueryMonitor; } } return null; } /** * Searches for an QueryMonitor from the list of orderedQueryMonitors, that could be the successor of the specified QueryMonitor. * @param queryMonitor * @param orderedQueryMonitors * @return */ private QueryMonitor searchSuccessor(QueryMonitor queryMonitor, List<QueryMonitor> orderedQueryMonitors) { Set<AbstractBPMNElement> successors; for(QueryMonitor orderedQueryMonitor : orderedQueryMonitors){ successors = new HashSet<AbstractBPMNElement>(); if(queryMonitor.getQuery().getPatternQueryType().equals(PatternQueryType.STATETRANSITION)){ for(AbstractBPMNElement monitoredElement : queryMonitor.getQuery().getMonitoredElements()){ successors.addAll(monitoredElement.getSuccessors()); } //Wenn es keine StateTransition ist, beobacht die Query eine Component, also bekommt man den Nachfolger als ExitPoint der Component }else{ for(AbstractBPMNElement parent : processDecompositionTree.getParents(queryMonitor.getQuery().getMonitoredElements())){ if(parent instanceof Component){ Component parentComponent = (Component) parent; successors.add(parentComponent.getExitPoint()); } } } successors.retainAll(orderedQueryMonitor.getQuery().getMonitoredElements()); if(!successors.isEmpty()){ return orderedQueryMonitor; } } return null; } /** * The method searches for exclusiveness violations between several pathes in a XOR component. If a second execution path * is monitored for a XOR component, it will be treated as a exclusiveness-violation. */ private void searchForExclusivenessViolations() { for(QueryMonitor queryMonitor : processInstanceMonitor.getQueryMonitorsWithQueryType(PatternQueryType.XOR)){ List<QueryMonitor> subQueryMonitors = processInstanceMonitor.getSubQueryMonitors(queryMonitor); if(!subQueryMonitors.contains(null) && !queryMonitor.isInLoop()){ int subQueriesFinished = 0; for(QueryMonitor subQueryMonitor : subQueryMonitors){ if(subQueryMonitor.isFinished()){ subQueriesFinished++; } } if(subQueriesFinished > 1){ for(QueryMonitor subQueryMonitor : subQueryMonitors){ subQueryMonitor.addViolationStatus(ViolationStatus.Exclusiveness); } } } } } /** * This method searches for cooccurence-Violations. If a process instance reaches the last monitorable element * and some elements are still running, these elements are missing and are marked with this violation status. */ private void searchForOccurenceViolations() { // Misssing kann sich nur auf StateTransitions beziehen? //Ohne Schleife: Falls Element noch Running ist --> Missing AbstractBPMNElement endEvent = null; for (AbstractBPMNElement element : processDecompositionTree.getLeafElements()) { if(element instanceof BPMNEndEvent) { endEvent = element; } } if(endEvent != null && processDecompositionTree != null){ AbstractBPMNElement lastMonitorableElement = getNearestMonitorablePredecessor(endEvent, processDecompositionTree.getLeafElements()); Set<QueryMonitor> lastElementQueryMonitors = processInstanceMonitor.getQueryMonitorsWithMonitoredElements(Arrays.asList(lastMonitorableElement)); if(!lastElementQueryMonitors.isEmpty()){ QueryMonitor lastElementQueryMonitor = lastElementQueryMonitors.iterator().next(); if(lastElementQueryMonitor.isTerminated()){ //Ohne Schleife for(QueryMonitor runningQueryMonitor : processInstanceMonitor.getQueryMonitorsWithStatus(QueryStatus.Started)){ //StateTransitions if(runningQueryMonitor.getQuery().getPatternQueryType().equals(PatternQueryType.STATETRANSITION)){ runningQueryMonitor.addViolationStatus(ViolationStatus.Missing); runningQueryMonitor.setQueryStatus(QueryStatus.Aborted); abortParentQueries(runningQueryMonitor); } } } } } } /** * Searches for cooccurence and exclusiveness violations in loop components. * A more accurate distinction is not possible for the monitoring of loop components. */ private void searchForLoopViolations() { //LoopComponents untersuchen: Wenn ExecutionCount unterschiedlich in einer LoopComponent, dann LoopViolation für LoopComponent //außer SubComponent ist wieder eine Loop for(QueryMonitor queryMonitor : processInstanceMonitor.getQueryMonitorsWithQueryType(PatternQueryType.LOOP)){ int loopExecutionCount = queryMonitor.getExecutionCount(); List<QueryMonitor> subQueryMonitors = processInstanceMonitor.getSubQueryMonitors(queryMonitor); if(!subQueryMonitors.contains(null)){ for(QueryMonitor subQueryMonitor : subQueryMonitors){ if(!subQueryMonitor.getQuery().getPatternQueryType().equals(PatternQueryType.LOOP)){ if(subQueryMonitor.getExecutionCount() != loopExecutionCount){ queryMonitor.addViolationStatus(ViolationStatus.Loop); break; } } } } } } /** * Aborts recursevily all parent queries for the specified query, that are running. * @param queryMonitor */ private void abortParentQueries(QueryMonitor queryMonitor) { SushiPatternQuery parentQuery = queryMonitor.getQuery().getParentQuery(); if(parentQuery != null){ QueryMonitor parentQueryMonitor = processInstanceMonitor.getQueryMonitorForQuery(parentQuery); if(parentQueryMonitor != null){ if(parentQueryMonitor.isRunning()){ parentQueryMonitor.setQueryStatus(QueryStatus.Aborted); } abortParentQueries(parentQueryMonitor); } } } private AbstractBPMNElement getNearestMonitorablePredecessor(AbstractBPMNElement sourceElement, Set<AbstractBPMNElement> considerableElements) { if(sourceElement.hasMonitoringPointsWithEventType() && !(sourceElement instanceof Component)){ return sourceElement; } //Map aufbauen mit möglichen Elementen und Abstand/Rekursionstiefe zum Startelement Map<AbstractBPMNElement, Integer> elements = new HashMap<AbstractBPMNElement, Integer>(); Set<AbstractBPMNElement> visitedElements = new HashSet<AbstractBPMNElement>(); visitedElements.add(sourceElement); getMonitorablePredecessors(sourceElement.getPredecessors(), elements, considerableElements, 1, visitedElements); //Nähstes Element ermitteln int minDepth = Integer.MAX_VALUE; AbstractBPMNElement nearestElement = null; for(AbstractBPMNElement element : elements.keySet()){ if(elements.get(element) < minDepth){ minDepth = elements.get(element); nearestElement = element; } } return nearestElement; } private void getMonitorablePredecessors(Set<AbstractBPMNElement> predecessors, Map<AbstractBPMNElement, Integer> elements, Set<AbstractBPMNElement> considerableElements, int depth, Set<AbstractBPMNElement> visitedElements) { for(AbstractBPMNElement predecessor : predecessors){ if(considerableElements.contains(predecessor) && !visitedElements.contains(predecessor)){ visitedElements.add(predecessor); if(predecessor.hasMonitoringPointsWithEventType() && !(predecessor instanceof Component)){ elements.put(predecessor, depth); } getMonitorablePredecessors(predecessor.getPredecessors(), elements, considerableElements, ++depth, visitedElements); } } } public ProcessInstanceMonitor getProcessInstanceMonitor() { return processInstanceMonitor; } public void setProcessInstanceMonitor(ProcessInstanceMonitor processInstanceMonitor) { this.processInstanceMonitor = processInstanceMonitor; } }