فهرست منبع

Wee supporting hierarchical calls and paralelism

marcioantfilho 6 ماه پیش
والد
کامیت
3db46b8ce5
30فایلهای تغییر یافته به همراه3063 افزوده شده و 1152 حذف شده
  1. 40 0
      src/main/java/ua/be/wee/controller/BranchContext.java
  2. 905 390
      src/main/java/ua/be/wee/controller/EnactmentControllerMVC.java
  3. 21 5
      src/main/java/ua/be/wee/controller/PMTrigger.java
  4. 3 3
      src/main/java/ua/be/wee/controller/rest/PTController.java
  5. 39 0
      src/main/java/ua/be/wee/dto/EventView.java
  6. 235 14
      src/main/java/ua/be/wee/model/EnactmentController.java
  7. 78 0
      src/main/java/ua/be/wee/model/ExecutionFlowManager.java
  8. 1 1
      src/main/java/ua/be/wee/model/NamedElement.java
  9. 76 0
      src/main/java/ua/be/wee/model/Token.java
  10. 5 1
      src/main/java/ua/be/wee/model/nodes/Activity.java
  11. 33 0
      src/main/java/ua/be/wee/model/nodes/BranchActivities.java
  12. 28 0
      src/main/java/ua/be/wee/model/nodes/InvokingActivity.java
  13. 1 0
      src/main/java/ua/be/wee/model/nodes/Node.java
  14. 1 0
      src/main/java/ua/be/wee/model/nodes/ports/ControlOutputPort.java
  15. 9 0
      src/main/java/ua/be/wee/model/pt/EndActivityEvent.java
  16. 8 4
      src/main/java/ua/be/wee/model/pt/Event.java
  17. 68 0
      src/main/java/ua/be/wee/model/pt/ExecutionToken.java
  18. 11 1
      src/main/java/ua/be/wee/model/pt/PT.java
  19. 16 2
      src/main/java/ua/be/wee/model/pt/StartActivityEvent.java
  20. 34 0
      src/main/java/ua/be/wee/model/pt/TreeNode.java
  21. 23 0
      src/main/java/ua/be/wee/model/repository/FusekiWrapper.java
  22. 254 1
      src/main/java/ua/be/wee/model/repository/NodeRespository.java
  23. 54 46
      src/main/java/ua/be/wee/model/repository/PMRepository.java
  24. 645 546
      src/main/java/ua/be/wee/model/repository/PTRepository.java
  25. 4 4
      src/main/java/ua/be/wee/model/util/AsyncHttpClientService.java
  26. 122 0
      src/main/java/ua/be/wee/service/EnactmentService.java
  27. 149 96
      src/main/resources/templates/enact.html
  28. 30 16
      src/main/resources/templates/enactEnd.html
  29. 168 21
      src/main/resources/templates/endEnactment.html
  30. 2 1
      src/main/resources/templates/pms.html

+ 40 - 0
src/main/java/ua/be/wee/controller/BranchContext.java

@@ -0,0 +1,40 @@
+package ua.be.wee.controller;
+
+import java.util.List;
+import java.util.Map;
+import java.util.ArrayList;
+
+public class BranchContext {
+    private String pmName;
+    private String branchId;
+    private String invokingActivityName;
+    private List<PMTrigger> availableActs;
+    private List<Map<String, Object>> endableActs;
+    
+    public BranchContext(String pmName, String branchId, String invokingActivityName) {
+        this.pmName = pmName;
+        this.branchId = branchId;
+        this.invokingActivityName = invokingActivityName;
+        this.availableActs = new ArrayList<>();
+        this.endableActs = new ArrayList<>();
+    }
+
+    public String getDisplayName() {
+        if (invokingActivityName != null && !invokingActivityName.trim().isEmpty()) {
+            return pmName + " (Invoked by " + invokingActivityName + ")";
+        }
+        return pmName;
+    }
+
+    // Getters e Setters
+    public String getPmName() { return pmName; }
+    public void setPmName(String pmName) { this.pmName = pmName; }
+    public String getBranchId() { return branchId; }
+    public void setBranchId(String branchId) { this.branchId = branchId; }
+    public String getInvokingActivityName() { return invokingActivityName; }
+    public void setInvokingActivityName(String invokingActivityName) { this.invokingActivityName = invokingActivityName; }
+    public List<PMTrigger> getAvailableActs() { return availableActs; }
+    public void setAvailableActs(List<PMTrigger> availableActs) { this.availableActs = availableActs; }
+    public List<Map<String, Object>> getEndableActs() { return endableActs; }
+    public void setEndableActs(List<Map<String, Object>> endableActs) { this.endableActs = endableActs; }
+}

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 905 - 390
src/main/java/ua/be/wee/controller/EnactmentControllerMVC.java


+ 21 - 5
src/main/java/ua/be/wee/controller/PMTrigger.java

@@ -7,11 +7,14 @@ import ua.be.wee.model.nodes.ports.ControlInputPort;
 public class PMTrigger {
 	
 	private FinalNode node;
-	
+	private ControlInputPort port;
+	private String branchId;
+	private String invokingActivityName; // New field to store the invoking activity's name
+
 	public FinalNode getNode() {
 		return node;
 	}
-
+	
 	public void setNode(FinalNode node) {
 		this.node = node;
 	}
@@ -24,8 +27,6 @@ public class PMTrigger {
 		this.port = port;
 	}
 
-	private ControlInputPort port;
-	
 	public String getName() {
 		if (port != null) {
 			return (port.getActivity() instanceof AutomatedActivity ? "AUT: " + port.getActivity().getName() + " VIA PORT: " + port.getName() :  port.getActivity().getName() + " VIA PORT: " + port.getName());
@@ -42,4 +43,19 @@ public class PMTrigger {
 		}
 	}
 
-}
+	public String getBranchId() { 
+		return branchId; 
+	}
+	
+	public void setBranchId(String branchId) { 
+		this.branchId = branchId; 
+	}
+
+	public String getInvokingActivityName() {
+		return invokingActivityName;
+	}
+
+	public void setInvokingActivityName(String invokingActivityName) {
+		this.invokingActivityName = invokingActivityName;
+	}
+}

+ 3 - 3
src/main/java/ua/be/wee/controller/rest/PTController.java

@@ -35,9 +35,9 @@ public class PTController {
 	}
 	
 	@CrossOrigin
-	@GetMapping("/traces/events/{traceiri}")
-	public List<Event> getEvents(@PathVariable String traceiri) throws Exception {
-		return ptRepository.getEvents(traceiri);
+	@GetMapping("/traces/events/{traceiri}/{branchId}")
+	public List<Event> getEvents(@PathVariable String traceiri, @PathVariable String branchId) throws Exception {
+		return ptRepository.getEvents(traceiri, branchId);
 	}
 	
 	@CrossOrigin

+ 39 - 0
src/main/java/ua/be/wee/dto/EventView.java

@@ -0,0 +1,39 @@
+package ua.be.wee.dto;
+
+public class EventView {
+    private String type;
+    private String label;
+    private String target;
+
+    public EventView() {}
+
+    public EventView(String type, String label, String target) {
+        this.type = type;
+        this.label = label;
+        this.target = target;
+    }
+
+    public String getType() {
+        return type;
+    }
+
+    public void setType(String type) {
+        this.type = type;
+    }
+
+    public String getLabel() {
+        return label;
+    }
+
+    public void setLabel(String label) {
+        this.label = label;
+    }
+
+    public String getTarget() {
+        return target;
+    }
+
+    public void setTarget(String target) {
+        this.target = target;
+    }
+}

+ 235 - 14
src/main/java/ua/be/wee/model/EnactmentController.java

@@ -4,17 +4,29 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 
+import org.apache.jena.query.QuerySolution;
+import org.apache.jena.query.ResultSet;
+import org.apache.jena.rdf.model.RDFNode;
 import org.json.JSONException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Component;
 
+import ua.be.wee.controller.PMTrigger;
 import ua.be.wee.model.nodes.Activity;
 import ua.be.wee.model.nodes.Artifact;
 import ua.be.wee.model.nodes.AutomatedActivity;
 import ua.be.wee.model.nodes.AutomatedStatus;
+import ua.be.wee.model.nodes.FinalNode;
+import ua.be.wee.model.nodes.ForkJoinNode;
+import ua.be.wee.model.nodes.InvokingActivity;
+import ua.be.wee.model.nodes.Node;
 import ua.be.wee.model.nodes.ports.ControlInputPort;
 import ua.be.wee.model.nodes.ports.ControlOutputPort;
 import ua.be.wee.model.nodes.ports.DataInputPort;
@@ -23,6 +35,7 @@ import ua.be.wee.model.pt.EndActivityEvent;
 import ua.be.wee.model.pt.Event;
 import ua.be.wee.model.pt.PT;
 import ua.be.wee.model.pt.StartActivityEvent;
+import ua.be.wee.model.pt.StartTraceEvent;
 import ua.be.wee.model.pt.TraceArtifact;
 import ua.be.wee.model.repository.NodeRespository;
 import ua.be.wee.model.repository.PMRepository;
@@ -46,6 +59,9 @@ public class EnactmentController {
 	@Autowired
 	private FileStorageService storageService;
 	
+	@Autowired
+	private ExecutionFlowManager executionFlowManager;
+	
 	public List<PM> getAllPMs() {
 		return pmRepo.getAllPMs();
 	}
@@ -56,39 +72,218 @@ public class EnactmentController {
 		return pm;
 	}
 
-	public List<Pair<String,String>> findNextNodes(String iri, String trace) throws Exception {
-		return pmRepo.findNextNodes(iri, trace); 
+	public List<Pair<String,String>> findNextNodes(String iri, String trace, ExecutionFlowManager executionFlowManager) throws Exception {
+		return pmRepo.findNextNodes(iri, trace, executionFlowManager); 
 	}
 
 	public PT createTrace(PM pm) throws Exception {
 		return traceRepo.createTrace(pm);
 	}
+	
+	public ExecutionFlowManager getExecutionFlowManager() {
+	    return executionFlowManager;
+	}
+
+	public String addStartEvent(PT pt, ControlInputPort port, Activity act, List<TraceArtifact> arts, String branchId) throws Exception {
+	    String iri = traceRepo.createStartEvent(pt, port, act, arts, branchId); 
+	    pmRepo.checkJoinSync(port.getIri(), pt.getIri());
+	    return iri;
+	}
+	
+	public String getParentBranchId(String childBranchId) {
+	    return traceRepo.getParentBranchId(childBranchId);
+	}
+
+	public void addEndEvent(PT pt, List<TraceArtifact> arts, ControlOutputPort p, String branchId) throws Exception {
+	    traceRepo.createEndEvent(pt, arts, p, branchId);
+	}
+	
+	public List<PMTrigger> getAvailableActivities(PT pt, PM pm, String branchId, Map<String, String> invokingActivityMap) throws Exception {
+	    List<Event> events = getEvents(pt.getIri(), branchId);
+	    
+	    Set<String> startedActivities = events.stream()
+	        .filter(e -> e instanceof StartActivityEvent)
+	        .map(e -> ((StartActivityEvent) e).getRelatesTo().getActivity().getIri())
+	        .collect(Collectors.toSet());
+
+	    Set<String> frontierNodes = new HashSet<>();
+
+	    if (events.isEmpty() || (events.size() == 1 && events.get(0) instanceof StartTraceEvent)) {
+	        frontierNodes.add(pm.getInitial().getIri());
+	    } else {
+	        for (Event e : events) {
+	            if (e instanceof EndActivityEvent) {
+	                frontierNodes.add(((EndActivityEvent) e).getRelatesTo().getIri());
+	            }
+	        }
+	    }
+
+	    for (Node node : pm.getNodes()) {
+	        if (node instanceof ForkJoinNode) {
+	            if (((ForkJoinNode) node).getNextNodes().size() > 1) {
+	                 frontierNodes.add(node.getIri());
+	            }
+	        }
+	    }
+	    
+	    Map<String, String> potentialNextMap = new LinkedHashMap<>();
+	    for (String nodeIri : frontierNodes) {
+	        for (Pair<String, String> pair : findNextNodes(nodeIri, pt.getIri(), this.executionFlowManager)) {
+	            potentialNextMap.put(pair.getFst(), pair.getSnd());
+	        }
+	    }
+	    
+	    List<Pair<String, String>> filteredIris = new ArrayList<>();
+	    for (Map.Entry<String, String> entry : potentialNextMap.entrySet()) {
+	        String activityIri = entry.getValue();
+	        if (activityIri == null || !startedActivities.contains(activityIri)) {
+	            filteredIris.add(new Pair<>(entry.getKey(), activityIri));
+	        }
+	    }
+	    
+	    return generatePMTriggers(pm, filteredIris, branchId, invokingActivityMap.get(branchId));
+	}
+
+	private List<PMTrigger> generatePMTriggers(PM pm, List<Pair<String, String>> iris, String branchId, String invokingActivityName) {
+	    List<PMTrigger> triggers = new ArrayList<>();
+	    for (Pair<String, String> pair : iris) {
+	        PMTrigger trigger = new PMTrigger();
+	        String nodeIri = (pair.getSnd() != null) ? pair.getSnd() : pair.getFst();
+	        Node node = pm.getNode(nodeIri);
 
-	public void addStartEvent(PT pt, ControlInputPort port, Activity act, List<TraceArtifact> arts) throws Exception {
-		traceRepo.createStartEvent(pt,port,act,arts);
-		pmRepo.checkJoinSync(port.getIri(),pt.getIri());
+	        if (node == null) continue;
+
+	        if (pair.getSnd() != null && node instanceof Activity) {
+	            Activity activity = (Activity) node;
+	            ControlInputPort port = activity.getCtrlInPorts().stream()
+	                    .filter(ctr -> ctr.getIri().equals(pair.getFst()))
+	                    .findFirst().orElse(null);
+	            if (port != null) {
+	                trigger.setPort(port);
+	                trigger.setBranchId(branchId);
+	                trigger.setInvokingActivityName(invokingActivityName);
+	                triggers.add(trigger);
+	            }
+	        } else if (node instanceof FinalNode) {
+	            trigger.setNode((FinalNode) node);
+	            trigger.setBranchId(branchId);
+	            triggers.add(trigger);
+	        }
+	    }
+	    return triggers;
+	}
+	
+	private PMTrigger generatePMTrigger(PM pm, ControlInputPort port, String branchId, String invokingActivityName) {
+	    if (port == null) {
+	        return null;
+	    }
+	    PMTrigger trigger = new PMTrigger();
+	    trigger.setPort(port);
+	    trigger.setBranchId(branchId);
+	    trigger.setInvokingActivityName(invokingActivityName);
+	    return trigger;
 	}
 
-	public void addEndEvent(PT pt, List<TraceArtifact> arts, ControlOutputPort p) throws Exception {
-		traceRepo.createEndEvent(pt,arts,p);	
+	
+	public List<PMTrigger> getRunningActivities(PT pt, PM pm, String branchId, Map<String, String> invokingActivityMap) throws Exception {
+	    List<Event> events = getEvents(pt.getIri(), branchId);
+	    List<PMTrigger> runningActs = new ArrayList<>();
+
+	    for (Event event : events) {
+	        if (event instanceof StartActivityEvent) {
+	            StartActivityEvent startEvent = (StartActivityEvent) event;
+	            Activity activity = startEvent.getRelatesTo().getActivity();
+
+	            if (activity instanceof InvokingActivity) {
+	                continue;
+	            }
+
+	            boolean ended = events.stream()
+	                    .filter(e -> e instanceof EndActivityEvent)
+	                    .map(e -> (EndActivityEvent) e)
+	                    .anyMatch(e -> e.getRelatesTo().getActivity().getIri().equals(activity.getIri()));
+
+	            if (!ended) {
+	                boolean tokenExists = this.executionFlowManager.getActiveTokens(pt.getIri())
+	                        .stream()
+	                        .anyMatch(t -> t.getCurrentNode().getIri().equals(activity.getIri()));
+
+	                if (!tokenExists) {
+	                    System.out.println("[getRunningActivities] Token created for: " + activity.getIri() + " on branch: " + branchId);
+	                    this.executionFlowManager.createExecutionFlow(pt, activity, branchId, invokingActivityMap.get(branchId));
+	                }
+
+	                PMTrigger trigger = generatePMTrigger(pm, startEvent.getRelatesTo(), branchId, invokingActivityMap.get(branchId));
+	                if (trigger != null) {
+	                    runningActs.add(trigger);
+	                }
+	            }
+	        }
+	    }
+	    System.out.println("[getRunningActivities] Returning " + runningActs.size() + " triggers for branch: " + branchId);
+	    return runningActs;
 	}
 
 	public Event addEndTraceEvent(String iri, String previous, String pmIRI) throws Exception {
 		return traceRepo.createEndTraceEvent(iri,previous, pmIRI);
 		
 	}
+	
+	public String generateBranchId(String traceIri, String parentBranch) {
+	    return traceRepo.generateBranchId(traceIri, parentBranch);
+	}
+	
+	public List<Event> getEvents(String traceIri, String branchId) throws Exception {
+	    return traceRepo.getEvents(traceIri, branchId);
+	}
+	
+	public String getParentTraceIri(String traceIri) {
+	    return nodeRepo.getParentTraceIri(traceIri);
+	}
 
 	public void updatePT(PT pt) throws Exception {
 		traceRepo.updatePT(pt);
-		
+	}
+	
+	public List<Event> getAllEventsForTrace(String traceIri) throws Exception {
+		return traceRepo.getAllEventsForTrace(traceIri);
+	}
+	
+	public String getPmIriForTrace(String traceIri) {
+		return traceRepo.getPmIriForTrace(traceIri);
+	}
+	
+	public List<StartTraceEvent> getActiveTraces(String pmiri) throws Exception {
+	    return traceRepo.getActiveTraces(pmiri);
+	}
+	
+	public String createStartTraceEvent(PT parentPt, PM pm, String eventIri, String parentTraceIri) throws Exception {
+	    return traceRepo.createStartTraceEvent(parentPt, pm, eventIri, parentTraceIri);
 	}
 
-	public void callAutomatedActivity(PT pt, AutomatedActivity aut, ControlInputPort port, List<TraceArtifact> arts) throws JSONException, IOException {
-		Map<String,Pair<String,String>> params = createParamsMap(aut, arts);
-		AsyncHttpClientService.asyncHTTPClient(pt, aut, port, params,this);
-		
+	public void callAutomatedActivity(PT pt, AutomatedActivity aut, ControlInputPort port, List<TraceArtifact> arts, String branchId) throws JSONException, IOException {
+	    Map<String,Pair<String,String>> params = createParamsMap(aut, arts);
+	    AsyncHttpClientService.asyncHTTPClient(pt, aut, port, params, this, branchId); 
+	}
+	
+	public PM getPMbyInvokedAct(String activityIri) throws Exception {
+
+	    Activity activity = nodeRepo.getActivityByIri(activityIri);
+	    if (activity == null) {
+	        throw new IllegalArgumentException("Atividade com o IRI fornecido não encontrada.");
+	    }
+
+	    String pmIri = nodeRepo.getPMIRIByInvokedActivity(activityIri);
+	    PM pm = pmRepo.getPM(pmIri);
+
+	    if (pm == null) {
+	        throw new IllegalArgumentException("PM associado à atividade não encontrado.");
+	    }
+
+	    return pm;
 	}
 
+
 	private Map<String,Pair<String,String>> createParamsMap(AutomatedActivity aut, List<TraceArtifact> arts) {
 		Map<String,Pair<String,String>> params = new HashMap<String, Pair<String,String>>();
 		List<DataInputPort> datalInPorts = aut.getDatalInPorts();
@@ -115,6 +310,29 @@ public class EnactmentController {
 		}
 		return params;
 	}
+	
+	public List<Map<String, String>> getActiveBranches(PT pt) {
+	    List<Map<String, String>> activeBranches = new ArrayList<>();
+
+	    List<Token> tokens = executionFlowManager.getActiveTokens(pt.getIri());
+
+	    for (Token token : tokens) {
+	        Map<String, String> branchInfo = new HashMap<>();
+
+	        branchInfo.put("branchId", token.getBranchId());
+
+	        String invokingActivityName = token.getInvokingActivityName();
+	        branchInfo.put("invokingActivityName", invokingActivityName != null ? invokingActivityName : "Main");
+
+	        PM pm = token.getProcessModel();
+	        String pmName = (pm != null) ? pm.getName() : "Unknown";
+	        branchInfo.put("pmName", pmName);
+
+	        activeBranches.add(branchInfo);
+	    }
+
+	    return activeBranches;
+	}
 
 	public void uploadArtifact(InputStream is, String filename) throws IOException {
 		storageService.save(is,filename);	
@@ -151,7 +369,8 @@ public class EnactmentController {
 		}
 		return result;
 	}
-	
+
+
 	public boolean checkMatchingActivityEvents(StartActivityEvent ev1, EndActivityEvent ev2) {
 		String ir = ev1.getIri();
 		String ir2= ev2.getIri();
@@ -161,5 +380,7 @@ public class EnactmentController {
 		String sub2 = split2[1].substring(split2[1].indexOf('_'));
 		return split[0].equals(split2[0]) && sub1.equals(sub2);
 	}
+
+
 	
-}
+}

+ 78 - 0
src/main/java/ua/be/wee/model/ExecutionFlowManager.java

@@ -0,0 +1,78 @@
+package ua.be.wee.model;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.stereotype.Component;
+
+import ua.be.wee.model.nodes.Node;
+import ua.be.wee.model.pt.PT;
+
+
+@Component
+public class ExecutionFlowManager {
+	    private final Map<String, List<Token>> activeTokensMap = new HashMap<>();
+
+	    // Initialize execution flow
+	    public Token createExecutionFlow(PT processTrace, Node startNode, String branchId, String invokingActivityName) {
+	        Token token = new Token(startNode, processTrace, branchId, invokingActivityName);
+	        activeTokensMap.computeIfAbsent(processTrace.getIri(), k -> new ArrayList<>()).add(token);
+	        return token;
+	    }
+
+
+	    // Get active tokens for a trace
+	    public List<Token> getActiveTokens(String traceIri) {
+	        return activeTokensMap.getOrDefault(traceIri, new ArrayList<>());
+	    }
+
+	    // Remove token (when process ends or token finishes)
+	    public void removeToken(String traceIri, String tokenId) {
+	        List<Token> tokens = activeTokensMap.get(traceIri);
+	        if (tokens != null) {
+	            tokens.removeIf(token -> token.getId().equals(tokenId));
+	            if (tokens.isEmpty()) {
+	                activeTokensMap.remove(traceIri);
+	            }
+	        }
+	    }
+	    
+	    public void addToken(String traceIri, Token token) {
+	        activeTokensMap.computeIfAbsent(traceIri, k -> new ArrayList<>()).add(token);
+	    }
+
+
+	    // Fork: creates new tokens from a parent token
+	    public Token forkToken(String traceIri, Token parentToken, Node nextNode) {
+	        Token newToken = new Token(
+	            nextNode,
+	            parentToken.getProcessTrace(),
+	            parentToken.getBranchId(),
+	            parentToken.getInvokingActivityName()
+	        );
+	        getActiveTokens(traceIri).add(newToken);
+	        return newToken;
+	    }
+
+
+	    // Update current node of a token
+	    public void updateTokenCurrentNode(Token token, Node nextNode) {
+	        token.setCurrentNode(nextNode);
+	    }
+
+	    public void clearTokensForTrace(String traceIri) {
+	        activeTokensMap.remove(traceIri);
+	    }
+	    
+	    public void terminateAllFlowsForTrace(String traceIri) {
+	        List<Token> tokens = activeTokensMap.get(traceIri);
+	        if (tokens != null) {
+	            for (Token token : tokens) {
+	                token.terminate();
+	            }
+	            activeTokensMap.remove(traceIri);
+	        }
+	    }
+}

+ 1 - 1
src/main/java/ua/be/wee/model/NamedElement.java

@@ -5,4 +5,4 @@ public interface NamedElement {
 	public String getName();
 	
 	public void setName(String name);
-}
+}

+ 76 - 0
src/main/java/ua/be/wee/model/Token.java

@@ -0,0 +1,76 @@
+package ua.be.wee.model;
+
+import java.util.UUID;
+
+import ua.be.wee.model.nodes.Node;
+import ua.be.wee.model.pm.PM;
+import ua.be.wee.model.pt.PT;
+
+public class Token {
+    private final String id;
+    private Node currentNode;
+    private final PT processTrace;
+    private final PM processModel;
+    private final String branchId;
+    private final String invokingActivityName;
+    private boolean active;
+
+    public Token(Node currentNode, PT processTrace, String branchId, String invokingActivityName) {
+        this.id = UUID.randomUUID().toString();
+        this.currentNode = currentNode;
+        this.processTrace = processTrace;
+        this.processModel = processTrace.getPmEnacted();
+        this.branchId = branchId;
+        this.invokingActivityName = invokingActivityName;
+        this.active = true;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public Node getCurrentNode() {
+        return currentNode;
+    }
+
+    public void setCurrentNode(Node currentNode) {
+        this.currentNode = currentNode;
+    }
+
+    public PT getProcessTrace() {
+        return processTrace;
+    }
+
+    public PM getProcessModel() {
+        return processModel;
+    }
+
+    public String getBranchId() {
+        return branchId;
+    }
+
+    public String getInvokingActivityName() {
+        return invokingActivityName;
+    }
+
+    public boolean isActive() {
+        return active;
+    }
+
+    public void terminate() {
+        this.active = false;
+    }
+
+    @Override
+    public String toString() {
+        return "Token{" +
+                "id='" + id + '\'' +
+                ", currentNode=" + (currentNode != null ? currentNode.getIri() : "null") +
+                ", processModel=" + (processModel != null ? processModel.getName() : "null") +
+                ", processTrace='" + processTrace.getIri() + '\'' +
+                ", branchId='" + branchId + '\'' +
+                ", invokingActivityName='" + invokingActivityName + '\'' +
+                ", active=" + active +
+                '}';
+    }
+}

+ 5 - 1
src/main/java/ua/be/wee/model/nodes/Activity.java

@@ -113,7 +113,11 @@ public class Activity extends Node {
 			outputs.add(art);
 		}
 	}
-
+	
+	@Override
+	public boolean equals(Object obj) {
+		return getIri().equals(((Activity)obj).getIri());
+	}
 	
 
 }

+ 33 - 0
src/main/java/ua/be/wee/model/nodes/BranchActivities.java

@@ -0,0 +1,33 @@
+package ua.be.wee.model.nodes;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import ua.be.wee.controller.PMTrigger;
+
+public class BranchActivities {
+    private List<PMTrigger> acts;
+    private List<Map<String, Object>> endacts;
+
+    public BranchActivities() {
+        this.acts = new ArrayList<>();
+        this.endacts = new ArrayList<>();
+    }
+
+    public List<PMTrigger> getActs() {
+        return acts;
+    }
+
+    public void setActs(List<PMTrigger> acts) {
+        this.acts = acts;
+    }
+
+    public List<Map<String, Object>> getEndacts() {
+        return endacts;
+    }
+
+    public void setEndacts(List<Map<String, Object>> endacts) {
+        this.endacts = endacts;
+    }
+}

+ 28 - 0
src/main/java/ua/be/wee/model/nodes/InvokingActivity.java

@@ -0,0 +1,28 @@
+package ua.be.wee.model.nodes;
+
+import ua.be.wee.model.pm.PM;
+
+public class InvokingActivity extends Activity {
+    private String invokedPM;
+    private String branchId; // Novo campo
+
+    public String getInvokedPM() {
+        return invokedPM;
+    }
+
+    public void setInvokePM(String invokedPM) {
+        this.invokedPM = invokedPM;
+    }
+
+    public String getBranchId() {
+        return branchId;
+    }
+
+    public void setBranchId(String branchId) {
+        this.branchId = branchId;
+    }
+
+    public Activity getTargetActivity(PM pm) {
+        return pm.getNode(invokedPM) instanceof Activity ? (Activity) pm.getNode(invokedPM) : null;
+    }
+}

+ 1 - 0
src/main/java/ua/be/wee/model/nodes/Node.java

@@ -27,6 +27,7 @@ public abstract class Node implements NamedElement {
 	
 	public static final String ACTIVITY_IRI = "http://ua.be/sdo2l/vocabulary/formalisms/pm#Activity";
 	public static final String AUTOMATED_ACTIVITY_IRI = "http://ua.be/sdo2l/vocabulary/formalisms/pm#AutomatedActivity";
+	public static final String INVOKING_ACTIVITY_IRI = "http://ua.be/sdo2l/vocabulary/formalisms/pm#InvokingActivity";
 	public static final String INITIAL_IRI = "http://ua.be/sdo2l/vocabulary/formalisms/pm#Initial";
 	public static final String FINAL_IRI = "http://ua.be/sdo2l/vocabulary/formalisms/pm#Final";
 	public static final String FORKJOIN_IRI = "http://ua.be/sdo2l/vocabulary/formalisms/pm#ForkJoin";

+ 1 - 0
src/main/java/ua/be/wee/model/nodes/ports/ControlOutputPort.java

@@ -22,4 +22,5 @@ public class ControlOutputPort extends Port {
 	public void setActivity(Activity activity) {
 		this.activity = activity;
 	}
+	
 }

+ 9 - 0
src/main/java/ua/be/wee/model/pt/EndActivityEvent.java

@@ -18,6 +18,11 @@ public class EndActivityEvent extends Event {
 	@OneToMany
 	private List<TraceArtifact> producedArtifacts;
 	
+	@OneToOne
+	private StartActivityEvent correspondingStartEvent;
+	
+	private String branchId;
+	
 	public EndActivityEvent() {
 		producedArtifacts = new ArrayList<TraceArtifact>();
 	}
@@ -51,4 +56,8 @@ public class EndActivityEvent extends Event {
 		return null;
 	}
 	
+    public String getBranchId() { return branchId; }
+    
+    public void setBranchId(String branchId) { this.branchId = branchId; }
+	
 }

+ 8 - 4
src/main/java/ua/be/wee/model/pt/Event.java

@@ -32,6 +32,8 @@ public abstract class Event {
 	
 	private Timestamp timestamp;
 	
+	private String branchId;
+	
 	public String getIri() {
 		return iri;
 	}
@@ -51,10 +53,12 @@ public abstract class Event {
 	public String getTimestampF() {
 		return new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(timestamp);
 	}
-
-	
-	
-	
 	
+	public String getBranchId() {
+        return branchId;
+    }
 
+    public void setBranchId(String branchId) {
+        this.branchId = branchId;
+    }
 }

+ 68 - 0
src/main/java/ua/be/wee/model/pt/ExecutionToken.java

@@ -0,0 +1,68 @@
+package ua.be.wee.model.pt;
+
+import ua.be.wee.controller.PMTrigger;
+import ua.be.wee.model.pm.PM;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class ExecutionToken {
+    private String tokenId;
+    private PM pm;
+    private Event lastEvent;
+    private List<PMTrigger> nextActivities;
+    private String parentTokenId;
+
+    public ExecutionToken(String tokenId, PM pm, Event lastEvent) {
+        this.tokenId = tokenId;
+        this.pm = pm;
+        this.lastEvent = lastEvent;
+        this.nextActivities = new ArrayList<>();
+        this.parentTokenId = null;
+    }
+
+    public String getTokenId() {
+        return tokenId;
+    }
+
+    public void setTokenId(String tokenId) {
+        this.tokenId = tokenId;
+    }
+
+    public PM getPm() {
+        return pm;
+    }
+
+    public void setPm(PM pm) {
+        this.pm = pm;
+    }
+
+    public Event getLastEvent() {
+        return lastEvent;
+    }
+
+    public void setLastEvent(Event lastEvent) {
+        this.lastEvent = lastEvent;
+    }
+
+    public List<PMTrigger> getNextActivities() {
+        return nextActivities;
+    }
+
+    public void setNextActivities(List<PMTrigger> nextActivities) {
+        this.nextActivities = nextActivities;
+    }
+
+    public String getParentTokenId() {
+        return parentTokenId;
+    }
+
+    public void setParentTokenId(String parentTokenId) {
+        this.parentTokenId = parentTokenId;
+    }
+
+    public void updateNextActivities(List<PMTrigger> nextActs) {
+        this.nextActivities = new ArrayList<>(nextActs);
+    }
+}

+ 11 - 1
src/main/java/ua/be/wee/model/pt/PT.java

@@ -1,6 +1,7 @@
 package ua.be.wee.model.pt;
 
 
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.persistence.Entity;
@@ -21,13 +22,15 @@ public class PT {
 	private String name;
 	
 	@OneToMany
-	private List<Event> events;
+	private List<Event> events = new ArrayList<>();
 	
 	@OneToMany
 	private List<TraceArtifact> inputs;
 	
 	@OneToOne
 	private PM pmEnacted;
+	
+	private String parentTraceIri;
 
 	public PM getPmEnacted() {
 		return pmEnacted;
@@ -40,6 +43,10 @@ public class PT {
 	public String getIri() {
 		return iri;
 	}
+	
+	public String getParentTraceIri() {
+        return parentTraceIri;
+    }
 
 	public void setIri(String iri) {
 		this.iri = iri;
@@ -77,4 +84,7 @@ public class PT {
 		this.inputs = inputs;
 	}
 
+	public void setParentTraceIri(String parentTraceIri) {
+        this.parentTraceIri = parentTraceIri;
+    }
 }

+ 16 - 2
src/main/java/ua/be/wee/model/pt/StartActivityEvent.java

@@ -8,6 +8,7 @@ import javax.persistence.OneToMany;
 import javax.persistence.OneToOne;
 
 import ua.be.wee.model.nodes.ports.ControlInputPort;
+import ua.be.wee.model.nodes.ports.ControlOutputPort;
 
 @Entity
 public class StartActivityEvent extends Event {
@@ -15,9 +16,17 @@ public class StartActivityEvent extends Event {
 	@OneToOne
 	private ControlInputPort relatesTo;
 	
+	@OneToOne
+	private ControlOutputPort p;
+	
 	@OneToMany
 	private List<TraceArtifact> consumedArtifacts;
 	
+	@OneToOne
+	private EndActivityEvent correspondingEndEvent;
+	
+	private String branchId;
+	
 	public StartActivityEvent() {
 		consumedArtifacts = new ArrayList<TraceArtifact>();
 	}
@@ -50,7 +59,12 @@ public class StartActivityEvent extends Event {
 		}
 		return null;
 	}
-	
-	
+    
+    public String getBranchId() { return branchId; }
+    public void setBranchId(String branchId) { this.branchId = branchId; }
+
+	public void setRelatesTo(ControlOutputPort p) {
+		this.p = p;
+	}
 
 }

+ 34 - 0
src/main/java/ua/be/wee/model/pt/TreeNode.java

@@ -0,0 +1,34 @@
+package ua.be.wee.model.pt;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TreeNode {
+    private String name;
+    private String traceIri;
+    private List<TreeNode> children = new ArrayList<>();
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public String getTraceIri() {
+        return traceIri;
+    }
+
+    public void setTraceIri(String traceIri) {
+        this.traceIri = traceIri;
+    }
+
+    public List<TreeNode> getChildren() {
+        return children;
+    }
+
+    public void addChild(TreeNode child) {
+        children.add(child);
+    }
+}

+ 23 - 0
src/main/java/ua/be/wee/model/repository/FusekiWrapper.java

@@ -5,8 +5,12 @@ import org.apache.jena.query.ResultSet;
 import org.apache.jena.rdfconnection.RDFConnectionFuseki;
 import org.apache.jena.rdfconnection.RDFConnectionRemoteBuilder;
 import org.apache.jena.sparql.exec.http.QueryExecutionHTTP;
+import org.apache.jena.sparql.util.Context;
+import org.apache.jena.sparql.util.Symbol;
 import org.apache.jena.update.UpdateFactory;
 import org.apache.jena.update.UpdateRequest;
+import org.apache.jena.riot.RIOT;
+
 
 public class FusekiWrapper {
 	
@@ -20,6 +24,13 @@ public class FusekiWrapper {
 	public String getServiceURI() {
 		return serviceURI;
 	}
+	
+	// Just to prevent the Malformed Json error
+	static {
+		Symbol symJsonReaderLenient = Symbol.create("http://jena.apache.org/riot#jsonReaderLenient");
+		Context context = RIOT.getContext();
+		context.set(symJsonReaderLenient, true);
+	}
 
 	private FusekiWrapper() {
 		
@@ -66,6 +77,18 @@ public class FusekiWrapper {
 
 	}
 	
+	public boolean execAsk(String queryStr) {
+	    if (!"".equals(serviceURI)) {
+	        try (QueryExecution qExec = QueryExecutionHTTP.service(serviceURI, queryStr)) {
+	            return qExec.execAsk();
+	        } catch (Exception e) {
+	            e.printStackTrace();
+	            return false;
+	        }
+	    }
+	    throw new NullPointerException("Service URI not specified");
+	}
+	
 	public boolean testEndpoint() {
 		String query = "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n" 
 				+ "SELECT ?pm WHERE {\n"

+ 254 - 1
src/main/java/ua/be/wee/model/repository/NodeRespository.java

@@ -15,6 +15,7 @@ import ua.be.wee.model.nodes.AutomatedActivity;
 import ua.be.wee.model.nodes.FinalNode;
 import ua.be.wee.model.nodes.ForkJoinNode;
 import ua.be.wee.model.nodes.InitialNode;
+import ua.be.wee.model.nodes.InvokingActivity;
 import ua.be.wee.model.nodes.Node;
 import ua.be.wee.model.nodes.ports.ControlInputPort;
 import ua.be.wee.model.nodes.ports.ControlOutputPort;
@@ -27,6 +28,8 @@ public class NodeRespository {
 
 	@Autowired
 	private PMRepository pmRepo;
+	
+	//TODO Create getPMbyActivityIRI 
 
 	public List<Node> getNodes(String pmIri) throws Exception {
 		List<Node> nodes = new ArrayList<Node>();
@@ -42,7 +45,7 @@ public class NodeRespository {
 				+ "  	owl:sameAs <" + pm.getIri() + "> ;\n" 
 				+ "  	ob:hasObject ?node .\n" 
 				+ "  ?node a ?nodetype .\n"
-				+ "  FILTER (?nodetype in (pm:Initial, pm:Activity, pm:AutomatedActivity, pm:Final, pm:ForkJoin, pm:Artifact)) .      \n"
+				+ "  FILTER (?nodetype in (pm:Initial, pm:Activity, pm:AutomatedActivity, pm:InvokingActivity, pm:Final, pm:ForkJoin, pm:Artifact)) .      \n"
 				+ "}";
 		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
 		while (results.hasNext()) {
@@ -60,6 +63,92 @@ public class NodeRespository {
 		return nodes;
 
 	}
+	
+	public ControlInputPort getControlInputPort(String iri) throws Exception {
+	    String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
+	                 + "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+	                 + "SELECT ?name ?activity WHERE {\n"
+	                 + "  ?port owl:sameAs <" + iri + "> ;\n"
+	                 + "        a pm:CtrlInputPort ;\n"
+	                 + "        pm:hasName ?name ;\n"
+	                 + "        pm:ofActivity ?activity .\n"
+	                 + "}";
+	    ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
+	    if (rs.hasNext()) {
+	        QuerySolution sol = rs.next();
+	        ControlInputPort port = new ControlInputPort();
+	        port.setIri(iri);
+	        port.setName(sol.get("?name").toString());
+
+	        // Set activity
+	        String activityIri = sol.get("?activity").toString();
+	        Activity act = getGenericActivityByIri(activityIri);
+
+	        port.setAct(act);
+
+	        return port;
+	    } else {
+	        throw new RuntimeException("ControlInputPort not found for IRI: " + iri);
+	    }
+	}
+	
+	public Activity getGenericActivityByIri(String activityIri) throws Exception {
+	    String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
+	            + "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+	            + "SELECT ?type WHERE {\n"
+	            + "  <" + activityIri + "> a ?type .\n"
+	            + "}";
+
+	    ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
+
+	    List<String> types = new ArrayList<>();
+	    while (rs.hasNext()) {
+	        QuerySolution sol = rs.next();
+	        types.add(sol.get("?type").toString());
+	    }
+
+	    // Verificar do mais específico para o mais genérico
+	    if (types.stream().anyMatch(t -> t.endsWith("InvokingActivity"))) {
+	        return (Activity) createInvokingActivity(activityIri);
+	    } else if (types.stream().anyMatch(t -> t.endsWith("AutomatedActivity"))) {
+	        return (Activity) createAutomatedActivity(activityIri);
+	    } else if (types.stream().anyMatch(t -> t.endsWith("Activity"))) {
+	        return getActivityByIri(activityIri);
+	    } else {
+	        throw new RuntimeException("Unsupported node type for activity: " + types);
+	    }
+	}
+
+
+
+	public ControlOutputPort getControlOutputPort(String iri) throws Exception {
+	    String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
+	                 + "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+	                 + "SELECT ?name ?activity WHERE {\n"
+	                 + "  ?port owl:sameAs <" + iri + "> ;\n"
+	                 + "        a pm:CtrlOutputPort ;\n"
+	                 + "        pm:hasName ?name ;\n"
+	                 + "        pm:ofActivity ?activity .\n"
+	                 + "}";
+	    ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
+	    if (rs.hasNext()) {
+	        QuerySolution sol = rs.next();
+	        ControlOutputPort port = new ControlOutputPort();
+	        port.setIri(iri);
+	        port.setName(sol.get("?name").toString());
+
+	        // Set activity
+	        String activityIri = sol.get("?activity").toString();
+	        Activity act = getGenericActivityByIri(activityIri);
+
+	        port.setActivity(act);
+
+	        return port;
+	    } else {
+	        throw new RuntimeException("ControlOutputPort not found for IRI: " + iri);
+	    }
+	}
+
 
 	private void updateControlNodesReferences(List<Node> nodes) {
 
@@ -240,6 +329,9 @@ public class NodeRespository {
 		case Node.AUTOMATED_ACTIVITY_IRI:
 			node = createAutomatedActivity(iri.toString());
 			break;
+		case Node.INVOKING_ACTIVITY_IRI:
+			node = createInvokingActivity(iri.toString());
+			break;
 		case Node.INITIAL_IRI:
 			node = new InitialNode();
 			break;
@@ -351,6 +443,44 @@ public class NodeRespository {
 			throw new Exception("Query did not return any data: \n" + query );
 		}
 	}
+	
+	private Node createInvokingActivity(String iri) throws Exception {
+	    InvokingActivity act = new InvokingActivity();
+	    
+	    String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
+	            + "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+	            + "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n"
+	            + "SELECT ?act ?name ?invokedModel WHERE {\n"
+	            + "  ?act owl:sameAs <" + iri + ">;\n"
+	            + "       pm:invokesPM ?invokedModel ;\n"  
+	            + "       pm:hasName ?name .\n"
+	            + "}"
+	            ;
+
+	    ResultSet results = FusekiWrapper.getInstance().execQuery(query);
+	    if (results.hasNext()) {
+	        QuerySolution soln = results.nextSolution();
+	        
+	        RDFNode name = soln.get("?name");
+			RDFNode invokedModel = soln.get("?invokedModel");
+			
+	        
+	        act.setIri(iri);
+	        act.setName(name.toString());
+	        act.setInvokePM(invokedModel.toString());
+
+	        act.setCtrlInPorts(getCtrlInPorts(act));
+	        
+	        act.setCtrlOutPorts(getCtrlOutPorts(act));
+	        act.setDatalInPorts(getDataInPorts(act));
+	        act.setDataOutPorts(getDataOutPorts(act));
+
+	        return act;
+	    } else {
+	        throw new Exception("Query did not return any data: \n" + query);
+	    }
+	}
+
 
 	private List<DataOutputPort> getDataOutPorts(Activity act) {
 		List<DataOutputPort> list = new ArrayList<DataOutputPort>();
@@ -431,5 +561,128 @@ public class NodeRespository {
 		}
 		return list;
 	}
+	
+
+	public Activity getActivityByIri(String activityIri) {
+		// return PM IRI of InvokedAct
+	    String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
+	            + "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+	            + "SELECT ?act ?name ?t WHERE {\n"
+	            + "  ?act owl:sameAs <" + activityIri + ">;\n"
+	            + "       pm:hasName ?name ;\n"
+	            + "       pm:isTransformation ?t .\n"
+	           // temporary + "  ?t pm:hasType ?type .\n"
+	            + "}";
+
+	    ResultSet results = FusekiWrapper.getInstance().execQuery(query);
+
+	    if (results.hasNext()) {
+	        QuerySolution soln = results.nextSolution();
+	        RDFNode name = soln.get("?name");
+	       // RDFNode type = soln.get("?type");
+
+	        Activity act = new Activity();
+	        act.setIri(activityIri);
+	        act.setName(name.toString());
+	       // act.setTransformationName(type.toString());
+
+	        // Define as portas para a atividade
+	        act.setCtrlInPorts(getCtrlInPorts(act));
+	        act.setCtrlOutPorts(getCtrlOutPorts(act));
+	        act.setDatalInPorts(getDataInPorts(act));
+	        act.setDataOutPorts(getDataOutPorts(act));
+
+	        return act;
+	    } else {
+	        throw new RuntimeException("No activity found with IRI:" + activityIri);
+	    }
+	}
+
+	public String getPMIRIByInvokedActivity(String activityIri) {
+	    // query SPARQL to get PM IRI associated to InvokedActivity
+	    String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
+	            + "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+	            + "SELECT ?pmIri WHERE {\n"
+	            + "  ?act owl:sameAs <" + activityIri + ">;\n"
+	            + "       a pm:InvokingActivity  ;\n"
+	            + "       pm:invokesPM ?pmIri .\n"
+	            + "}";
+
+	    ResultSet results = FusekiWrapper.getInstance().execQuery(query);
+
+	    if (results.hasNext()) {
+	        QuerySolution soln = results.nextSolution();
+	        RDFNode pmIriNode = soln.get("?pmIri");
+
+	        if (pmIriNode != null) {
+	            return pmIriNode.toString(); 
+	        }
+	    }
+
+	    // if we don't find the PM, throw a exception
+	    throw new RuntimeException("No PM IRI found for InvokedActivity with IRI: " + activityIri);
+	}
+	
+	public String getActivityIRIByInvokedActivity(String invokedActivityIri) {
+	    String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
+	            + "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+	            + "SELECT ?activityIri WHERE {\n"
+	            + "  <" + invokedActivityIri + "> pm:hasPort ?inputPort .\n" // Get the input port of the InvokingActivity
+	            + "  ?inputPort a pm:CtrlInputPort ;\n"                    // Ensure it’s a control input port
+	            + "            pm:ctrlFrom ?outputPort .\n"              // Trace back to the output port
+	            + "  ?outputPort a pm:CtrlOutputPort ;\n"                 // Ensure it’s a control output port
+	            + "             pm:ofActivity ?activityIri .\n"          // Get the activity owning the output port
+	            + "  ?activityIri a pm:Activity .\n"                     // Confirm it’s an Activity
+	            + "} LIMIT 1";
+
+	    ResultSet results = FusekiWrapper.getInstance().execQuery(query);
+	    if (results.hasNext()) {
+	        QuerySolution soln = results.nextSolution();
+	        RDFNode activityIriNode = soln.get("?activityIri");
+	        if (activityIriNode != null) {
+	            System.out.println("Found previous activity: " + activityIriNode.toString());
+	            return activityIriNode.toString();
+	        }
+	    }
+	    System.out.println("WARNING: No previous activity found for InvokingActivity: " + invokedActivityIri);
+	    return null;
+	}
+	
+	public String getPMbyActivityIRI(String activityIri) {
+		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
+                + "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+                + "SELECT ?pmIri WHERE {\n"
+                + "  ?act owl:sameAs <" + activityIri + ">;\n"
+                + "       a pm:InvokingActivity ;\n"
+                + "       pm:invokesPM ?pmIri .\n"
+                + "}";
+
+        ResultSet results = FusekiWrapper.getInstance().execQuery(query);
+
+        if (results.hasNext()) {
+            QuerySolution soln = results.nextSolution();
+            RDFNode pmIriNode = soln.get("?pmIri");
+
+            if (pmIriNode != null) {
+                return pmIriNode.toString();
+            }
+        }
+
+        throw new RuntimeException("No PM IRI found for Activity with IRI: " + activityIri);
+    }
+
+
+	public String getParentTraceIri(String traceIri) {
+		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n" +
+					   "SELECT ?parentTrace WHERE {\n" +
+					   "  <" + traceIri + "> tr:invokedByTrace ?parentTrace .\n" +
+					   "}";
+		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
+		if (rs.hasNext()) {
+			return rs.nextSolution().getResource("?parentTrace").getURI();
+		}
+		return null;
+	}
+	
 
 }

+ 54 - 46
src/main/java/ua/be/wee/model/repository/PMRepository.java

@@ -8,6 +8,8 @@ import org.apache.jena.query.ResultSet;
 import org.apache.jena.rdf.model.RDFNode;
 import org.springframework.stereotype.Repository;
 
+import ua.be.wee.model.ExecutionFlowManager;
+import ua.be.wee.model.Token;
 import ua.be.wee.model.nodes.Node;
 import ua.be.wee.model.pm.PM;
 import ua.be.wee.model.pt.PT;
@@ -63,45 +65,47 @@ public class PMRepository {
 		
 	}
 
-	public List<Pair<String,String>> findNextNodes(String iri, String trace) throws Exception {
-		List<Pair<String,String>> list = new ArrayList<Pair<String,String>>();
-		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
-				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
-				+ "SELECT ?e ?nodetype ?act WHERE {\n"
-				+ "  ?node owl:sameAs <" + iri + "> .\n"
-				+ "  ?node pm:ctrlTo ?e .\n"
-				+ "  ?e a ?nodetype ;\n"
-				+ "  FILTER (?nodetype in (pm:CtrlInputPort, pm:Final, pm:ForkJoin)) \n"
-				+ "	 OPTIONAL {\n"
-				+ "		?e pm:ofActivity ?act .\n"
-				+ "	 }\n"
-				+ "}";
-		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
-		while (results.hasNext()) {
-			QuerySolution soln = results.nextSolution();
-			RDFNode type = soln.get("?nodetype");
-			Pair<String,String> pair = null;
-			if (type.toString().equals(Node.CTRL_INPUT_PORT_IRI)) {
-				pair = new Pair<String,String>(soln.get("?e").toString(),soln.get("?act").toString());
-			} else if (type.toString().equals(Node.FORKJOIN_IRI)) {
-				if (isJoin(soln.get("?e").toString())) {
-					if (checkJoin(soln.get("?e").toString(), trace, iri)) {
-						list.addAll(findNextNodes(soln.get("?e").toString(), trace));
-					}
-				} else {
-					list.addAll(findNextNodes(soln.get("?e").toString(), trace));
-				}
-			} else {
-				pair = new Pair<String,String>(soln.get("?e").toString(), null);
-			}
-			if (pair != null) {
-				list.add(pair);
-			}
-		}
-		return list;
+	public List<Pair<String,String>> findNextNodes(String iri, String trace, ExecutionFlowManager executionFlowManager) throws Exception {
+	    List<Pair<String,String>> list = new ArrayList<Pair<String,String>>();
+	    String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
+	            + "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+	            + "SELECT ?e ?nodetype ?act WHERE {\n"
+	            + "  ?node owl:sameAs <" + iri + "> .\n"
+	            + "  ?node pm:ctrlTo ?e .\n"
+	            + "  ?e a ?nodetype ;\n"
+	            + "  FILTER (?nodetype in (pm:CtrlInputPort, pm:Final, pm:ForkJoin, pm:Merge)) \n"
+	            + "	 OPTIONAL {\n"
+	            + "		?e pm:ofActivity ?act .\n"
+	            + "	 }\n"
+	            + "}";
+	    ResultSet results = FusekiWrapper.getInstance().execQuery(query);
+	    while (results.hasNext()) {
+	        QuerySolution soln = results.nextSolution();
+	        RDFNode type = soln.get("?nodetype");
+	        Pair<String,String> pair = null;
+	        if (type.toString().equals(Node.CTRL_INPUT_PORT_IRI)) {
+	            pair = new Pair<String,String>(soln.get("?e").toString(),soln.get("?act").toString());
+	        
+	        } else if (type.toString().equals(Node.FORKJOIN_IRI) || type.toString().equals("http://ua.be/sdo2l/vocabulary/formalisms/pm#Merge")) {
+	        
+	            if (isJoin(soln.get("?e").toString())) {
+	                if (checkJoin(soln.get("?e").toString(), trace, iri, executionFlowManager)) {
+	                    list.addAll(findNextNodes(soln.get("?e").toString(), trace, executionFlowManager));
+	                }
+	            } else {
+	                list.addAll(findNextNodes(soln.get("?e").toString(), trace, executionFlowManager));
+	            }
+	        } else {
+	            pair = new Pair<String,String>(soln.get("?e").toString(), null);
+	        }
+	        if (pair != null) {
+	            list.add(pair);
+	        }
+	    }
+	    return list;
 	}
 
-	private boolean checkJoin(String node, String trace, String input) throws Exception {
+	private boolean checkJoin(String node, String trace, String input, ExecutionFlowManager executionFlowManager) throws Exception {
 		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
 				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
 				+ "SELECT ?sync WHERE {\n"
@@ -113,14 +117,14 @@ public class PMRepository {
 		if (results.hasNext()) {
 			QuerySolution soln = results.nextSolution();
 			String sync = soln.get("?sync").toString();
-			return checkInputs(sync,node,trace,input);
+			return checkInputs(sync,node,trace,executionFlowManager, input);
 		} else {
 			createJoinSync(node,trace,input);
 			return false;
 		}
 	}
 
-	private boolean checkInputs(String sync, String node, String trace, String input) throws Exception {
+	private boolean checkInputs(String sync, String node, String trace, ExecutionFlowManager executionFlowManager, String input) throws Exception {
 		List<String> list = new ArrayList<String>();
 		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
 				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
@@ -137,22 +141,25 @@ public class PMRepository {
 			String inCtrl = soln.get("?inCtrl").toString();
 			list.add(inCtrl);
 		}
+		
 		if (list.size() == 1 && list.get(0).equals(input)) {
 			insertRealizedInputInJoinSync(sync,input);
 			return true; 
 		} else if (list.size() == 1 && !list.get(0).equals(input)) {
 			return false;
 		} else if (list.size() > 1) {
-			for (String inCtrl : list) {
-				if (inCtrl.equals(input)) {
-					insertRealizedInputInJoinSync(sync,input);
-				}
-			}
+			insertRealizedInputInJoinSync(sync, input); 
 		} else if (list.isEmpty()) {
 			return true;
 		}
-		return false;
-		
+		List<Token> activeTokens = executionFlowManager.getActiveTokens(trace);
+		if (activeTokens.isEmpty() && list.size() > 1) {
+			System.out.println("[checkInputs-Token] O RDF indica que faltam " + (list.size() -1) + " entradas para o join " + node + ", mas não há tokens ativos no processo. O processo pode estar travado.");
+		} else if (!activeTokens.isEmpty()){
+			 System.out.println("[checkInputs-Token] Verificação de consistência: " + activeTokens.size() + " tokens ainda ativos para o trace " + trace);
+		}
+
+		return false; 
 	}
 
 	private void insertRealizedInputInJoinSync(String sync, String input) throws Exception {
@@ -250,4 +257,5 @@ public class PMRepository {
 		
 	}
 	
+	
 }

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 645 - 546
src/main/java/ua/be/wee/model/repository/PTRepository.java


+ 4 - 4
src/main/java/ua/be/wee/model/util/AsyncHttpClientService.java

@@ -34,7 +34,7 @@ import ua.be.wee.model.pt.TraceArtifact;
 
 public class AsyncHttpClientService {
 	
-	public static void asyncHTTPClient(PT pt, AutomatedActivity act, ControlInputPort port, Map<String,Pair<String,String>> artifacts, EnactmentController control) throws JSONException, IOException {
+	public static void asyncHTTPClient(PT pt, AutomatedActivity act, ControlInputPort port, Map<String,Pair<String,String>> artifacts, EnactmentController control, String branchId) throws JSONException, IOException {
 		DefaultAsyncHttpClientConfig.Builder clientBuilder = Dsl.config().setConnectTimeout(Duration.ofMillis(act.getTimeout()));
 		AsyncHttpClient client = Dsl.asyncHttpClient(clientBuilder);
 		
@@ -103,7 +103,7 @@ public class AsyncHttpClientService {
 						}	
 					} 
 					Thread.sleep(500);
-					control.addEndEvent(pt, traceArts, ctrlOutPort);
+					control.addEndEvent(pt, traceArts, ctrlOutPort, branchId);
 					
 				} else {
 					throw new Exception("The response of automated activity " + act.getName() + " returned an unexpected status code: " + statusCode);
@@ -126,7 +126,7 @@ public class AsyncHttpClientService {
 					}
 					try {
 						if (ctrlOutPort != null) {
-							control.addEndEvent(pt, new ArrayList<>(), ctrlOutPort);
+							control.addEndEvent(pt, new ArrayList<>(), ctrlOutPort, branchId);
 						} else {
 							System.err.println("Missing 'error' control port!");
 						}
@@ -147,7 +147,7 @@ public class AsyncHttpClientService {
 					}
 					try {
 						if (ctrlOutPort != null) {
-							control.addEndEvent(pt, new ArrayList<>(), ctrlOutPort);
+							control.addEndEvent(pt, new ArrayList<>(), ctrlOutPort, branchId);
 						} else {
 							System.err.println("Missing 'error' control port!");
 						}

+ 122 - 0
src/main/java/ua/be/wee/service/EnactmentService.java

@@ -0,0 +1,122 @@
+package ua.be.wee.service;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import ua.be.wee.controller.PMTrigger;
+import ua.be.wee.controller.rest.PTController;
+import ua.be.wee.model.EnactmentController;
+import ua.be.wee.model.nodes.Activity;
+import ua.be.wee.model.nodes.InvokingActivity;
+import ua.be.wee.model.pm.PM;
+import ua.be.wee.model.pt.EndActivityEvent;
+import ua.be.wee.model.pt.Event;
+import ua.be.wee.model.pt.PT;
+import ua.be.wee.model.pt.StartActivityEvent;
+import ua.be.wee.model.repository.PTRepository;
+import ua.be.wee.model.util.Pair;
+import ua.be.wee.model.repository.NodeRespository;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+@Service
+public class EnactmentService {
+
+    // Injetando dependências necessárias
+    @Autowired
+    private EnactmentController controller; // Controlador para chamadas específicas, se necessário
+    @Autowired
+    private PTController ptController; // Outro controlador, se precisar
+    @Autowired
+    private PTRepository ptRepository; // Repositório para acessar dados
+    @Autowired
+    private NodeRespository nodeRepository; // Repositório de nós
+
+    /** Valida os atributos da sessão */
+    public boolean validateSessionAttributes(PT pt, Map<String, PM> pmMap) {
+        return pt != null && pmMap != null && !pmMap.isEmpty();
+    }
+
+    /** Recupera o PM selecionado com base no branchId */
+    public PM getSelectedPM(String branchId, Map<String, PM> pmMap) {
+        return pmMap.get(branchId);
+    }
+
+    /** Calcula as atividades finalizadas */
+    public List<Map<String, Object>> computeEndActs(List<Event> events, String branchId) {
+        List<String> startedActivities = new ArrayList<>();
+        List<Map<String, Object>> endActs = new ArrayList<>();
+        for (Event event : events) {
+            if (event instanceof StartActivityEvent) {
+                StartActivityEvent startEvent = (StartActivityEvent) event;
+                String activityIri = startEvent.getRelatesTo().getActivity().getIri();
+                boolean ended = events.stream()
+                        .filter(e -> e instanceof EndActivityEvent)
+                        .map(e -> (EndActivityEvent) e)
+                        .anyMatch(e -> e.getRelatesTo().getActivity().getIri().equals(activityIri));
+                if (!ended) {
+                    startedActivities.add(activityIri);
+                    Map<String, Object> endAct = new HashMap<>();
+                    endAct.put("activity", startEvent.getRelatesTo().getActivity());
+                    endAct.put("branchId", branchId);
+                    endActs.add(endAct);
+                }
+            }
+        }
+        return endActs;
+    }
+
+    public List<PMTrigger> computeStartActs(PT pt, PM pm, List<Event> events, String branchId, Map<String, String> invokingActivityMap) throws Exception {
+        if (events.isEmpty()) {
+            // MODIFICADO: Adicionado controller.getExecutionFlowManager() à chamada.
+            List<Pair<String, String>> iris = controller.findNextNodes(pm.getInitial().getIri(), pt.getIri(), controller.getExecutionFlowManager());
+            return findElements(pm, iris, branchId, invokingActivityMap.get(branchId));
+        }
+        Event lastEvent = events.get(events.size() - 1);
+        if (lastEvent instanceof EndActivityEvent) {
+            EndActivityEvent endEvent = (EndActivityEvent) lastEvent;
+            // MODIFICADO: Adicionado controller.getExecutionFlowManager() à chamada.
+            List<Pair<String, String>> iris = controller.findNextNodes(endEvent.getRelatesTo().getIri(), pt.getIri(), controller.getExecutionFlowManager());
+            return findElements(pm, iris, branchId, invokingActivityMap.get(branchId));
+        } else if (lastEvent instanceof StartActivityEvent) {
+            StartActivityEvent startEvent = (StartActivityEvent) lastEvent;
+            Activity activity = startEvent.getRelatesTo().getActivity();
+            if (activity instanceof InvokingActivity) {
+                InvokingActivity invokingActivity = (InvokingActivity) activity;
+                PM invokedPM = controller.getPM(invokingActivity.getInvokedPM());
+                // MODIFICADO: Adicionado controller.getExecutionFlowManager() à chamada.
+                List<Pair<String, String>> iris = controller.findNextNodes(invokedPM.getInitial().getIri(), pt.getIri(), controller.getExecutionFlowManager());
+                return findElements(invokedPM, iris, branchId, invokingActivity.getName());
+            } else {
+                return findStartActs(pt, branchId);
+            }
+        }
+        return findStartActs(pt, branchId);
+    }
+
+    public List<Map<String, String>> prepareActivityList(List<PMTrigger> acts, String pmId) {
+        return acts.stream()
+                .filter(act -> act.getBranchId().equals(pmId))
+                .map(act -> {
+                    Map<String, String> activityMap = new HashMap<>();
+                    activityMap.put("iri", act.getIri());
+                    String name = act.getName();
+                    if (act.getInvokingActivityName() != null) {
+                        name += " (invocado por " + act.getInvokingActivityName() + ")";
+                    }
+                    activityMap.put("name", name);
+                    return activityMap;
+                })
+                .collect(Collectors.toList());
+    }
+
+    private List<PMTrigger> findElements(PM pm, List<Pair<String, String>> iris, String branchId, String invokingActivityName) {
+        return new ArrayList<>(); // Placeholder
+    }
+
+    private List<PMTrigger> findStartActs(PT trace, String branchId) throws Exception {
+        // Implementação do método findStartActs aqui, se necessário
+        return new ArrayList<>(); // Placeholder
+    }
+}

+ 149 - 96
src/main/resources/templates/enact.html

@@ -1,110 +1,163 @@
 <!DOCTYPE html>
-<html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="base::layout(~{::title}, ~{::main})">
+<html lang="en" xmlns:th="http://www.thymeleaf.org"
+      th:replace="base::layout(~{::title}, ~{::main})">
 <head>
-  <title>[[${session.trace.name}]] Enactment</title>
+    <title>[[${session.trace.name}]] Enactment</title>
 </head>
 <body>
 <main>
-  <!-- TODO Use fragment inclusion expressions to hide the panel when no ongoing automated activities are present? -->
-  <article th:replace="fragments/automated.html :: activity(activities=${session.automated})"></article>
+    <section class="pt-6 pb-6 pl-5 pr-5">
+        <div class="columns">
+            <div class="column is-two-thirds">
+                <div class="tags are-large has-addons">
+                    <span class="tag">PM</span>
+                    <span class="tag is-primary">
+                        <span th:text="${session.pm.name}"></span>
+                    </span>
+                </div>
 
-  <section class="pt-6 pb-6 pl-5 pr-5">
-    <div class="columns">
-      <div class="column is-two-thirds">
-        <div class="tags are-large has-addons">
-          <span class="tag">PM</span>
-          <span class="tag is-primary">[[${session.pm.name}]]</span>
-        </div>
-        <div class="enactment pt-3">
-          <form class="form" th:action="@{/startAct}" method="post">
-            <th:block th:if="${session.acts != null && session.acts.size > 0}">
-              <!--  Options shown only when starting an activity  -->
-              <p>The selected Process Model supports the following activities. Choose the activity you want to start.</p>
-              <div class="field has-addons">
-                <div class="control is-expanded">
-                  <div class="select is-fullwidth">
-                    <select id="activities" name="iri"
-                            th:onchange="'window.location.href = \'' + @{/inarts} + '?iri=\' + encodeURIComponent(this.value) ' ">
-                      <option th:value="1">Select Activity</option>
-                      <option th:each="act: ${session.acts}" th:selected="${act.iri == current}" th:value="${act.iri}"
-                              th:text="${act.name}"></option>
-                    </select>
-                  </div>
+                <div th:if="${errorMessage}" class="notification is-danger">
+                    <p th:text="${errorMessage}"></p>
                 </div>
-                <div class="control" th:unless="${session.arts != null}"><button id="startActivity" type="submit" class="button is-primary" disabled>Start Activity</button></div>
-                <div class="control" th:if="${session.arts != null}"><button id="startActivity" type="submit" class="button is-primary">Start Activity</button></div>
-              </div>
-            </th:block>
-            <th:block th:if="${session.endacts != null && session.endacts.size > 0}">
-              <!--  Options shown only when ending an activity  -->
-              <p>The selected Process Model supports the following activities. Choose the activity you want to end.</p>
-              <div class="field has-addons">
-                <div class="control is-expanded">
-                  <div class="select is-fullwidth">
-                    <select id="activitiesend" name="activityend"
-                            th:onchange="'window.location.href = \'' + @{/endselect} + '?iri=\' + encodeURIComponent(this.value) ' ">
-                      <option th:value="1">Select Activity</option>
-                      <option th:each="actend: ${session.endacts}" th:selected="${actend.iri == currentend}"
-                              th:value="${actend.iri}" th:text="${actend.name}"></option>
-                    </select>
-                  </div>
+
+                <div class="enactment pt-3">
+                    <form th:action="@{/selectBranch}" method="post">
+                        <div class="field">
+                            <label class="label">Process Model Context</label>
+                            <div class="control">
+                                <div class="select is-fullwidth">
+                                    <select name="branchId" onchange="this.form.submit()" required>
+                                        <option value="">Select Process Model</option>
+                                        <option th:each="branch : ${activeBranches}"
+                                                th:value="${branch.branchId}"
+                                                th:text="${branch.pmName + (branch.invokingActivityName != null ? ' (Invoked by ' + branch.invokingActivityName + ')' : '')}"
+                                                th:selected="${branch.branchId == session.currentBranchId}">
+                                        </option>
+                                    </select>
+                                </div>
+                            </div>
+                        </div>
+                    </form>
+
+                    <div th:if="${#lists.size(availableActsBranches) > 0}">
+                        <form th:action="@{/startAct}" method="post">
+                            <input type="hidden" name="branchId" th:value="${session.currentBranchId}"/>
+                            <div class="field">
+                                <label class="label">Activities</label>
+                                <div class="control">
+                                    <div class="select is-fullwidth">
+                                        <select name="iri" required id="activity-selector">
+                                            <option value="">Select Activity</option>
+                                            <option th:each="act : ${session.acts}"
+                                                    th:value="${act.iri}"
+                                                    th:text="${act.port != null ? act.port.activity.name : 'End Process'}">
+                                            </option>
+                                        </select>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="control">
+                                <button class="button is-primary" type="submit" id="start-activity-button">Start Activity</button>
+                            </div>
+                        </form>
+                    </div>
+
+                    <div th:if="${#lists.size(runningActsBranches) > 0}" class="pt-5">
+                        <form th:action="@{/endselect}" method="get">
+							<p class="has-text-weight-bold">Select an activity to end</p>
+                            <div class="field">
+                                <label class="label">Activities to End</label>
+                                <div class="control">
+                                    <div class="select is-fullwidth">
+                                        <select name="iri" required>
+                                            <option value="">Select Activity</option>
+                                            <option th:each="endAct : ${session.endacts}"
+                                                    th:value="${endAct.activity.iri}"
+                                                    th:text="${endAct.activity.name}">
+                                            </option>
+                                        </select>
+                                    </div>
+                                </div>
+                            </div>
+                            <input type="hidden" name="branchId" th:value="${session.currentBranchId}"/>
+                            <div class="control">
+                                <button class="button is-danger" type="submit">End Activity</button>
+                            </div>
+                        </form>
+                    </div>
+
+                    <div th:if="${session.arts != null && session.arts.size() > 0}" class="artifacts pt-6">
+                        <p class="is-size-4 is-capitalized">Artifacts</p>
+                        <div th:each="art : ${session.arts}">
+                            <article class="media">
+                                <figure class="media-left">
+                                    <p class="image is-64x64">
+                                        <img src="https://bulma.io/images/placeholders/128x128.png" alt="Download">
+                                    </p>
+                                </figure>
+                                <div class="media-content">
+                                    <div class="content">
+                                        <p>
+                                            <strong th:text="${art.relatesTo.name}"></strong>
+                                            <small th:text="${art.relatesTo.type}"></small>
+                                            <br/>
+                                            <a th:href="@{|/files/${art.fileExtension}/${art.location}|}">Download</a>
+                                        </p>
+                                    </div>
+                                </div>
+                            </article>
+                        </div>
+                    </div>
                 </div>
-              </div>
-            </th:block>
-            <div class="control">
-              <button type="submit" class="button is-primary" id="endEnactment" th:if="${endBool}">End Enactment</button>
             </div>
-          </form>
 
-          <div class="artifacts pt-6" th:if="${session.arts != null && session.arts.size > 0}">
-            <p class="is-size-4 is-capitalized">Artifacts</p>
-            <th:block th:each="art : ${session.arts}">
-              <article class="media">
-                <figure class="media-left">
-                  <p class="image is-64x64">
-                    <img src="https://bulma.io/images/placeholders/128x128.png" alt="Download arrow">
-                  </p>
-                </figure>
-                <div class="media-content">
-                  <div class="content">
-                    <p>
-                      <strong th:text="${art.relatesTo.name}"></strong> <small th:text="${art.relatesTo.type}"></small>
-                      <br>
-                      <a
-                        th:if="${art.fileExtension == 'xopp' || art.fileExtension == 'csv' || art.fileExtension == 'drawio'}"
-                        th:href="@{|${storageURL}/files/${art.fileExtension}/${art.location}|}">Download</a>
-                      <a
-                        th:if="${art.fileExtension != null && art.fileExtension != 'xopp' && art.fileExtension != 'csv' && art.fileExtension != 'drawio'}"
-                        th:href="@{|${storageURL}/files/file/${art.location}|}">Download</a>
-                    </p>
-                  </div>
+            <div class="column">
+                <div class="tags are-large has-addons">
+                    <span class="tag">PT</span>
+                    <span class="tag is-primary" th:text="${session.trace.name}"></span>
                 </div>
-              </article>
-            </th:block>
-          </div>
-        </div>
-      </div>
-      <div class="column">
-        <div class="tags are-large has-addons">
-          <span class="tag">PT</span>
-          <span class="tag is-primary">[[${session.trace.name}]]</span>
-        </div>
-        <div class="details">
-          <p th:inline="text"><strong>Started at: </strong><span
-            class="accent">[[${session.trace.events[0].timestampF}]]</span></p>
-          <th:block th:each="ev : ${session.trace.events}">
-            <p th:if="${ev.class.name == 'ua.be.wee.model.pt.StartActivityEvent'}"><strong>Begin: </strong><span
-              class="accent" th:text="${ev.relatesTo.activity.name}"></span></p>
-            <p th:if="${ev.class.name == 'ua.be.wee.model.pt.EndActivityEvent'}"><strong>End: </strong><span
-              class="accent" th:text="${ev.relatesTo.activity.name}"></span></p>
-            <p
-              th:if="${ev.class.name == 'ua.be.wee.model.pt.EndActivityEvent' || ev.class.name == 'ua.be.wee.model.pt.StartActivityEvent'}">
-              <strong>Port: </strong><span class="accent">[[${ev.relatesTo.name}]]</span></p>
-          </th:block>
+
+                <div class="details">
+                    <div class="timeline">
+                        <th:block th:each="ev : ${eventViews}">
+                            <div class="timeline-item">
+                                <div class="event-content">
+                                    <p>
+                                        <strong th:text="${ev.label + ': '}"></strong>
+                                        <span class="accent" th:text="${ev.target}"></span>
+                                    </p>
+                                </div>
+                                <div class="arrow">↓</div>
+                            </div>
+                        </th:block>
+                    </div>
+                </div>
+            </div>
         </div>
-      </div>
-    </div>
-  </section>
+    </section>
+
+    <script th:inline="javascript">
+        document.addEventListener('DOMContentLoaded', () => {
+            const activitySelector = document.getElementById('activity-selector');
+            const startActivityButton = document.getElementById('start-activity-button');
+
+            if (activitySelector && startActivityButton) {
+                const updateButtonText = () => {
+                    const selectedOption = activitySelector.options[activitySelector.selectedIndex];
+
+                    if (selectedOption && selectedOption.text === 'End Process') {
+                        startActivityButton.textContent = 'End PM';
+                    } else {
+                        startActivityButton.textContent = 'Start Activity';
+                    }
+                };
+
+                activitySelector.addEventListener('change', updateButtonText);
+
+                updateButtonText();
+            }
+        });
+    </script>
 </main>
 </body>
-</html>
+</html>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 30 - 16
src/main/resources/templates/enactEnd.html


+ 168 - 21
src/main/resources/templates/endEnactment.html

@@ -1,29 +1,176 @@
 <!DOCTYPE html>
 <html lang="en" xmlns:th="http://www.thymeleaf.org" th:replace="base::layout(~{::title}, ~{::main})">
 <head>
-  <title>[[${session.trace.name}]] Enactment End</title>
+    <title>[[${pm.name}]] Enactment End</title>
+    <style>
+        .timeline {
+            position: relative;
+            padding-left: 4rem;
+            margin: 1rem 0;
+        }
+
+        .timeline-item {
+            position: relative;
+            padding-bottom: 2rem;
+        }
+
+        .timeline-item:not(:last-child)::before {
+            content: '';
+            position: absolute;
+            left: -2.35rem;
+            top: 0;
+            width: 2px;
+            height: 100%;
+            background-color: #dbdbdb;
+        }
+
+        .timeline-icon {
+            position: absolute;
+            left: -3rem;
+            top: 0.2rem;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            width: 1.5rem;
+            height: 1.5rem;
+            border-radius: 50%;
+            background-color: #7a7a7a;
+            color: white;
+            font-size: 0.8rem;
+        }
+        .timeline-icon.is-start-trace { background-color: #23d160; }
+        .timeline-icon.is-end-trace { background-color: #ff3860; }
+        .timeline-icon.is-start-activity { background-color: #3273dc; }
+        .timeline-icon.is-end-activity { background-color: #666; }
+        .timeline-icon.is-invoke { background-color: #ffdd57; color: rgba(0,0,0,.7); }
+        .timeline-icon.is-fork { background-color: #7957d5; }
+
+        .timeline-content .heading {
+            font-size: 0.9rem;
+            color: #7a7a7a;
+            margin-bottom: 0.25rem;
+        }
+        .timeline-content .title {
+            font-size: 1.1rem;
+            font-weight: 600;
+        }
+        .timeline-content .subtitle {
+            font-size: 0.9rem;
+            color: #4a4a4a;
+        }
+        .is-hierarchical {
+            border-left: 3px solid #ffdd57;
+            padding-left: 1rem;
+            background-color: #fffbeb;
+        }
+        .is-forked {
+            border-left: 3px solid #7957d5;
+            padding-left: 1rem;
+            background-color: #f4f0ff;
+        }
+    </style>
 </head>
 <body>
 <main>
-  <!-- TODO Use fragment inclusion expressions to hide the panel when no ongoing automated activities are present? -->
-  <article th:replace="fragments/automated.html :: activity(activities=${session.automated})"></article>
-
-  <section class="pt-6 pb-6 pl-5 pr-5">
-    <p th:inline="text">Enactment of the Process Model: <span class="accent">[[${session.pm.name}]]</span> has ended!</p>
-    <p>You can review the details about the Process Trace.</p>
-    <div class="tags are-large has-addons">
-      <span class="tag">PT</span>
-      <span class="tag is-primary">[[${session.trace.name}]]</span>
-    </div>
-    <p th:inline="text"><strong>Started at: </strong><span class="accent">[[${session.trace.events[0].timestampF}]]</span></p>
-    <th:block th:each="ev : ${session.trace.events}">
-      <p th:if="${ev.class.name == 'ua.be.wee.model.pt.StartActivityEvent'}"><strong>Begin: </strong></p>
-      <p th:if="${ev.class.name == 'ua.be.wee.model.pt.EndActivityEvent'}"><strong>End: </strong></p>
-      <p th:if="${ev.class.name == 'ua.be.wee.model.pt.EndActivityEvent' || ev.class.name == 'ua.be.wee.model.pt.StartActivityEvent'}" th:text="${ev.relatesTo.activity.name}"></p>
-      <p th:if="${ev.class.name == 'ua.be.wee.model.pt.EndActivityEvent' || ev.class.name == 'ua.be.wee.model.pt.StartActivityEvent'}">port: [[${ev.relatesTo.name}]]</p>
-      <p th:if="${ev.class.name == 'ua.be.wee.model.pt.EndTraceEvent'}"><strong>Ended at: </strong><span class="accent">[[${ev.timestampF}]]</span></p>
-    </th:block>
-  </section>
+    <section class="section">
+        <div class="container">
+            <h1 class="title">Execution Finished</h1>
+            <p class="subtitle" th:inline="text">
+                The enactment of the Process Model <strong class="has-text-primary">[[${pm.name}]]</strong> has been completed.
+            </p>
+            <div class="tags are-medium has-addons">
+                <span class="tag">Process Trace</span>
+                <span class="tag is-primary" th:text="${trace.iri}"></span>
+            </div>
+            <hr/>
+
+            <h2 class="title is-4 mt-5">Execution Timeline</h2>
+
+            <div class="box content">
+                <div class="timeline">
+                    <th:block th:each="ev : ${trace.events}">
+                        <div th:if="${not (ev instanceof T(ua.be.wee.model.pt.EndTraceEvent))}">
+                            <th:block th:with="baseTraceUri=${(ev.branchId != null and #strings.contains(ev.branchId, '/branches/')) ? #strings.substringBefore(ev.branchId, '/branches/') + '/branches/' : ''},
+                                               branchPath=${(ev.branchId != null and #strings.contains(ev.branchId, '/branches/')) ? #strings.substringAfter(ev.branchId, baseTraceUri) : ''},
+                                               indentationLevel=${(branchPath != '' ? #strings.length(branchPath) - #strings.length(#strings.replace(branchPath, '/', '')) : 0)}">
+
+                                <div class="timeline-item" th:style="'margin-left: ' + (${indentationLevel} * 3) + 'rem;'">
+
+                                    <div th:if="${ev instanceof T(ua.be.wee.model.pt.StartTraceEvent) and ev.iri == trace.iri}">
+                                        <span class="timeline-icon is-start-trace">▶</span>
+                                        <div class="timeline-content">
+                                            <p class="heading" th:text="${ev.timestampF}"></p>
+                                            <p class="title is-5">Process Started</p>
+                                            <p class="subtitle" th:text="${trace.pmEnacted.name}"></p>
+                                        </div>
+                                    </div>
+
+                                    <div th:if="${ev instanceof T(ua.be.wee.model.pt.StartActivityEvent)}">
+                                        <th:block th:with="activity = ${ev.relatesTo.activity}">
+                                            <div th:if="${activity instanceof T(ua.be.wee.model.nodes.InvokingActivity)}">
+                                                <div th:if="${indentationLevel == 0}">
+                                                    <span class="timeline-icon is-fork">⑂</span>
+                                                    <div class="timeline-content is-forked">
+                                                        <p class="heading" th:text="${ev.timestampF}"></p>
+                                                        <p class="title is-5">Fork Execution</p>
+                                                        <p class="subtitle" th:text="'Executing branch: ' + ${activity.name}"></p>
+                                                        <small th:text="'Branch: ' + ${ev.branchId}"></small>
+                                                    </div>
+                                                </div>
+                                                <div th:if="${indentationLevel > 0}">
+                                                    <span class="timeline-icon is-invoke">↳</span>
+                                                    <div class="timeline-content is-hierarchical">
+                                                        <p class="heading" th:text="${ev.timestampF}"></p>
+                                                        <p class="title is-5">Hierarchical Call</p>
+                                                        <p class="subtitle" th:text="'Executing activity: ' + ${activity.name}"></p>
+                                                        <small th:text="'Branch: ' + ${ev.branchId}"></small>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                            <div th:unless="${activity instanceof T(ua.be.wee.model.nodes.InvokingActivity)}">
+                                                <span class="timeline-icon is-start-activity">●</span>
+                                                <div class="timeline-content">
+                                                    <p class="heading" th:text="${ev.timestampF}"></p>
+                                                    <p class="title is-5">Activity Started</p>
+                                                    <p class="subtitle" th:text="${activity.name}"></p>
+                                                    <small th:text="'Port: ' + ${ev.relatesTo.name} + ' | Branch: ' + ${ev.branchId}"></small>
+                                                </div>
+                                            </div>
+                                        </th:block>
+                                    </div>
+
+                                    <div th:if="${ev instanceof T(ua.be.wee.model.pt.EndActivityEvent)}">
+                                        <span class="timeline-icon is-end-activity">○</span>
+                                        <div class="timeline-content">
+                                            <p class="heading" th:text="${ev.timestampF}"></p>
+                                            <p class="title is-5">Activity Ended</p>
+                                            <p class="subtitle" th:text="${ev.relatesTo.activity.name}"></p>
+                                            <small th:text="'Port: ' + ${ev.relatesTo.name} + ' | Branch: ' + ${ev.branchId}"></small>
+                                        </div>
+                                    </div>
+                                </div>
+                            </th:block>
+                        </div>
+                    </th:block>
+
+                    <th:block th:each="ev, iterStat : ${trace.events}">
+                        <th:block th:if="${ev instanceof T(ua.be.wee.model.pt.EndTraceEvent) and ev.iri == trace.iri + '_end'}">
+                            <div th:if="${iterStat.index == 0 or trace.events[iterStat.index-1].iri != ev.iri}">
+                                 <div class="timeline-item">
+                                    <span class="timeline-icon is-end-trace">■</span>
+                                    <div class="timeline-content">
+                                        <p class="heading" th:text="${ev.timestampF}"></p>
+                                        <p class="title is-5">Process Finished</p>
+                                        <p class="subtitle" th:text="${pm.name}"></p>
+                                    </div>
+                                </div>
+                            </div>
+                        </th:block>
+                    </th:block>
+                </div>
+            </div>
+        </div>
+    </section>
 </main>
 </body>
-</html>
+</html>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 2 - 1
src/main/resources/templates/pms.html