package sushi.query.bpmn;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import sushi.bpmn.decomposition.BondComponent;
import sushi.bpmn.decomposition.Component;
import sushi.bpmn.decomposition.SubProcessComponent;
import sushi.bpmn.element.AbstractBPMNElement;
import sushi.bpmn.element.AttachableElement;
import sushi.bpmn.element.BPMNBoundaryEvent;
import sushi.bpmn.element.BPMNEventType;
import sushi.bpmn.element.BPMNIntermediateEvent;
import sushi.esper.SushiStreamProcessingAdapter;
import sushi.event.SushiEventType;
import sushi.event.attribute.SushiAttributeTree;
import sushi.event.collection.SushiTree;
import sushi.eventhandling.Broker;
import sushi.monitoring.bpmn.BPMNQueryMonitor;
import sushi.process.SushiProcess;
import sushi.query.SushiPatternQuery;
/**
* This class defines the abstract factory methods to generate the {@link SushiPatternQuery}s.
* The queries are created in the concrete subclasses.
* @author micha
*/
public abstract class AbstractPatternQueryFactory {
protected SushiTree<AbstractBPMNElement> processDecompositionTree;
protected PatternQueryGenerator patternQueryGenerator;
private static List<String> queryNames = new ArrayList<String>();
/**
* Constructor to create queries with a query factory.
* @param patternQueryGenerator
*/
public AbstractPatternQueryFactory(PatternQueryGenerator patternQueryGenerator){
this.patternQueryGenerator = patternQueryGenerator;
this.processDecompositionTree = patternQueryGenerator.getProcessDecompositionTree();
}
/**
* This method generates a {@link SushiPatternQuery} for the element.
* The type of the query is depending on the concrete PatternQueryFactory.
* If the element is a component, then the children of the element are used for the query.
* The catchingMonitorableElement can specify an attached element for this query.
* If the catchingMonitorableElement is monitored, the query finishes immediately.
* The parentQuery is the query, that contains this new created one.
* @param element
* @param catchingMonitorableElement
* @param parentQuery
* @return
*/
protected abstract SushiPatternQuery generateQuery(AbstractBPMNElement element, AbstractBPMNElement catchingMonitorableElement, SushiPatternQuery parentQuery);
/**
* Registers the query at Esper and for the {@link BPMNQueryMonitor}.
* @param query
*/
protected void registerQuery(SushiPatternQuery query) {
this.addPatternEventTypeToEsper(query.getTitle());
BPMNQueryMonitor.getInstance().addQueryForProcess(query, SushiProcess.findByBPMNProcess(patternQueryGenerator.getSushiRPSTTree().getProcess()));
query.setListener(query.addToEsper(SushiStreamProcessingAdapter.getInstance()));
}
/**
* This method is indicated to update a query that is already registered at Esper.
* @param query
*/
protected void updateQuery(SushiPatternQuery query) {
query.updateForEsper(SushiStreamProcessingAdapter.getInstance());
}
/**
* @param name
* @return
*/
public SushiEventType addPatternEventTypeToEsper(String name){
name = name.replaceAll(" ", "");
SushiEventType patternEventType = new SushiEventType(name, new SushiAttributeTree(), "Timestamp");
//TODO: Muss EventType wirklich gespeichert werden?
Broker.send(patternEventType);
return patternEventType;
}
/**
* This method generates the actual query, that is needed for Esper
* and can be stored as the queryString in the {@link SushiPatternQuery}.
* The concrete generated string depends on the patternOperator,
* which is used as an infix operator while query creation.
* @param component
* @param patternOperator
* @param catchingMonitorableElement
* @param parentQuery
* @return
*/
protected String generateQueryString(Component component, EsperPatternOperators patternOperator, AbstractBPMNElement catchingMonitorableElement, SushiPatternQuery parentQuery) {
int elementsWithMonitoringPoints = 0;
StringBuilder sequencePatternQueryString = new StringBuilder();
//TODO: Liefert auch noch Gateways am Rand, die nicht betrachtet werden sollten
//TODO: Timer-Events
List<AbstractBPMNElement> orderedChildren = this.orderElements(component);
for(AbstractBPMNElement element : orderedChildren){
//Falls Element Component rekursiv tiefer aufrufen
StringBuilder queryPart = new StringBuilder();
if(element instanceof Component && element.hasMonitoringPoints() && element.hasMonitoringPointsWithEventType()){
SushiPatternQuery subQuery = new PatternQueryFactory(patternQueryGenerator).generateQuery(element, catchingMonitorableElement, parentQuery);
addQueryRelationship(parentQuery, subQuery);
queryPart.append("EVERY S" + elementsWithMonitoringPoints + "=");
queryPart.append(subQuery.getTitle());
}
//Element hat Attached Timer
else if(
element instanceof AttachableElement &&
element.hasMonitoringPointsWithEventType() &&
((AttachableElement)element).hasAttachedIntermediateEvent() &&
((AttachableElement)element).getAttachedIntermediateEvent().getIntermediateEventType() == BPMNEventType.Timer &&
orderedChildren.contains(((AttachableElement)element).getAttachedIntermediateEvent())){
//Püfen, ob Boundary Event auch im Polygon ist, sonst wird der Timer hier nicht abgefragt
AttachableElement attachableElement = (AttachableElement)element;
BPMNBoundaryEvent boundaryEvent = attachableElement.getAttachedIntermediateEvent();
//Timer-Query bauen
System.err.println("Timer Query");
SushiPatternQuery subQuery = new TimerQueryFactory(patternQueryGenerator).generateQuery(element, catchingMonitorableElement, parentQuery);
addQueryRelationship(parentQuery, subQuery);
queryPart.append("EVERY S" + elementsWithMonitoringPoints + "=");
queryPart.append(subQuery.getTitle());
//Timer-Element und Boundary-Timer aus den orderedChildren entfernen (ModifyException?)
orderedChildren.removeAll(Arrays.asList(attachableElement, boundaryEvent));
}
//Element ist IntermediateTimer
else if(element instanceof BPMNIntermediateEvent && ((BPMNIntermediateEvent)element).getIntermediateEventType().equals(BPMNEventType.Timer)){
//Timer-Query bauen
System.err.println("Timer Query");
SushiPatternQuery subQuery = new TimerQueryFactory(patternQueryGenerator).generateQuery(element, catchingMonitorableElement, parentQuery);
addQueryRelationship(parentQuery, subQuery);
queryPart.append("EVERY S" + elementsWithMonitoringPoints + "=");
queryPart.append(subQuery.getTitle());
}
//Normales Element: Activity Lifecycle Query
else if(element.hasMonitoringPointsWithEventType()){
SushiPatternQuery subQuery = new StateTransitionQueryFactory(patternQueryGenerator).generateQuery(element, catchingMonitorableElement, parentQuery);
System.out.println(subQuery.getTitle() + ": " + subQuery.getQueryString());
queryPart.append("EVERY S" + elementsWithMonitoringPoints + "=");
queryPart.append(subQuery.getTitle());
} else {
continue;
}
if(elementsWithMonitoringPoints == 0){ //Erstes Element
sequencePatternQueryString.append("SELECT * FROM PATTERN [(");
sequencePatternQueryString.append(queryPart);
} else {
sequencePatternQueryString.append(" " + patternOperator.operator + " " + queryPart);
}
elementsWithMonitoringPoints++;
}
if(catchingMonitorableElement == null){
sequencePatternQueryString.append(")]");
} else {
sequencePatternQueryString.append(") " + EsperPatternOperators.XOR.operator + " EVERY C1=");
sequencePatternQueryString.append(catchingMonitorableElement.getMonitoringPoints().get(0).getEventType().getTypeName());
sequencePatternQueryString.append("]");
}
if(patternOperator != EsperPatternOperators.XOR){
//gleiche ProcessInstanceID-Bedingung anhängen
sequencePatternQueryString.append(" WHERE sushi.esper.SushiUtils.isIntersectionNotEmpty({");
for(int j = 0; j < elementsWithMonitoringPoints; j++){
if(j == elementsWithMonitoringPoints - 1){ //letztes Element --> kein Komma
sequencePatternQueryString.append("S" + j + ".ProcessInstances");
} else {
sequencePatternQueryString.append("S" + j + ".ProcessInstances,");
}
}
sequencePatternQueryString.append("})");
}
return sequencePatternQueryString.toString();
}
/**
* Orders the elements in a list in their sequential order in a process.
* Elements have to be consecutive.
* @param component
* @return
*/
protected List<AbstractBPMNElement> orderElements(Component component) {
List<AbstractBPMNElement> componentChildren = new ArrayList<AbstractBPMNElement>(processDecompositionTree.getChildren(component));
List<AbstractBPMNElement> orderedElements = new ArrayList<AbstractBPMNElement>();
int componentChildrenAmount = componentChildren.size();
//Bonds brauchen nicht sortiert werden
if(component instanceof BondComponent){
return processDecompositionTree.getChildren(component);
}
//Startelement einfügen
AbstractBPMNElement startElement = component.getSourceElement();
if(startElement == null){
throw new RuntimeException("No source element for component!");
}
orderedElements.add(startElement);
componentChildren.remove(startElement);
for(int i = 0; i < componentChildrenAmount; i++){
AbstractBPMNElement lastOrderedElement = orderedElements.get(orderedElements.size() - 1);
// Set<AbstractBPMNElement> successors = lastOrderedElement.getSuccessors();
Set<AbstractBPMNElement> successors = getSuccessorsWithComponents(lastOrderedElement, componentChildren);
List<AbstractBPMNElement> componentChildrenCopy = new ArrayList<AbstractBPMNElement>(componentChildren);
componentChildrenCopy.retainAll(successors);
if(componentChildrenCopy.size() > 0){
orderedElements.add(componentChildrenCopy.get(0));
}
}
if(componentChildrenAmount != orderedElements.size()){
throw new RuntimeException("Elements could not be ordered!");
}
return orderedElements;
}
private Set<AbstractBPMNElement> getSuccessorsWithComponents(AbstractBPMNElement predecessor, List<AbstractBPMNElement> elements) {
Set<AbstractBPMNElement> successors;
if(predecessor instanceof Component){
Component component = (Component) predecessor;
if(predecessor instanceof SubProcessComponent){
SubProcessComponent subProcessComponent = (SubProcessComponent) predecessor;
successors = new HashSet<AbstractBPMNElement>(subProcessComponent.getSubProcess().getSuccessors());
} else {
successors = new HashSet<AbstractBPMNElement>(component.getSinkElement().getSuccessors());
//Falls Component eine Schleife ist, könnten hier auch Elemente aus der Component als Nachfolger auftauchen
successors.removeAll(component.getChildren());
}
//Falls Component eine Schleife ist, könnten hier auch Elemente aus der Component als Nachfolger auftauchen
successors.removeAll(component.getChildren());
} else {
successors = new HashSet<AbstractBPMNElement>(predecessor.getSuccessors());
}
for(AbstractBPMNElement element : elements){
if(element instanceof Component){
Component component = (Component) element;
if(successors.contains(component.getSourceElement())){
successors.add(element);
}
}
}
return successors;
}
/**
* @param element
* @param visitedElements
* @param successingMonitorableElements
*/
protected void traverseSuccessingMonitorableElements(AbstractBPMNElement element, Set<AbstractBPMNElement> visitedElements, Set<AbstractBPMNElement> successingMonitorableElements) {
if(!visitedElements.contains(element)){
visitedElements.add(element);
if(element.hasMonitoringPoints()){
successingMonitorableElements.add(element);
} else {
for(AbstractBPMNElement child : element.getSuccessors()){
traverseSuccessingMonitorableElements(child, visitedElements, successingMonitorableElements);
}
}
}
}
/**
* Connects the two queries as parent and child.
* @param parent
* @param child
*/
protected void addQueryRelationship(SushiPatternQuery parent, SushiPatternQuery child){
child.setParentQuery(parent);
if(parent != null){
parent.addChildQueries(child);
}
}
/**
* This method generates a unique name for each query.
* So that query names can be used as event types and do not intersect.
* @param prefix
* @return
*/
protected String generateQueryName(String prefix){
String queryName;
do{
queryName = prefix + new Date().getTime();
} while(queryNames.contains(queryName));
queryNames.add(queryName);
return queryName;
}
}