marcioantfilho 4 месяцев назад
Родитель
Сommit
49574f85a6

+ 3 - 1
.gitignore

@@ -81,4 +81,6 @@ Temporary Items
 .apdisk
 
 /target/
-.idea
+.idea
+/.apt_generated/
+/.apt_generated_tests/


Разница между файлами не показана из-за своего большого размера
+ 601 - 610
src/main/java/ua/be/wee/controller/EnactmentControllerMVC.java


+ 70 - 0
src/main/java/ua/be/wee/controller/FileController.java

@@ -0,0 +1,70 @@
+package ua.be.wee.controller;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
+
+import ua.be.wee.service.FileStorageServiceImpl;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+@Controller
+public class FileController {
+
+    @Autowired
+    private FileStorageServiceImpl fileStorageService; 
+
+    @GetMapping("/files/{fileExtension}/{fileName:.+}")
+    public ResponseEntity<StreamingResponseBody> downloadFile(
+            @PathVariable String fileExtension,
+            @PathVariable String fileName) {
+        
+        try {
+            CloseableHttpResponse response = fileStorageService.loadAsHttpResponse(fileName);
+            
+            int statusCode = response.getStatusLine().getStatusCode();
+            
+            if (statusCode != HttpStatus.OK.value()) {
+                response.close();
+                return ResponseEntity.status(statusCode).build();
+            }
+
+            HttpEntity entity = response.getEntity();
+            if (entity == null) {
+                response.close();
+                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
+            }
+
+            StreamingResponseBody responseBody = (OutputStream out) -> {
+                try (response; InputStream inputStream = entity.getContent()) {
+                    inputStream.transferTo(out);
+                }
+            };
+
+            HttpHeaders headers = new HttpHeaders();
+            headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"");
+            
+            if (entity.getContentType() != null) {
+                headers.setContentType(MediaType.parseMediaType(entity.getContentType().getValue()));
+            } else {
+                headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
+            }
+            
+            return new ResponseEntity<>(responseBody, headers, HttpStatus.OK);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
+        }
+    }
+}

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

@@ -9,7 +9,7 @@ public class PMTrigger {
 	private FinalNode node;
 	private ControlInputPort port;
 	private String branchId;
-	private String invokingActivityName; // New field to store the invoking activity's name
+	private String invokingActivityName;
 
 	public FinalNode getNode() {
 		return node;

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

@@ -9,6 +9,7 @@ import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RestController;
 
+import ua.be.wee.model.EnactmentController;
 import ua.be.wee.model.pt.Event;
 import ua.be.wee.model.pt.StartTraceEvent;
 import ua.be.wee.model.repository.FusekiWrapper;
@@ -20,6 +21,8 @@ public class PTController {
 	@Autowired
 	private PTRepository ptRepository;
 	
+	@Autowired
+    private EnactmentController enactmentController;
 	
 	@Autowired
 	public PTController(@Value("${endpoint}")final String endpoint) throws Exception {
@@ -52,7 +55,12 @@ public class PTController {
 		return ptRepository.getConcludedTraces(pmiri);
 	}
 	
-
+	@CrossOrigin
+    @GetMapping("/traces/events/all/{traceiri}")
+    public List<Event> getAllEventsForTrace(@PathVariable String traceiri) throws Exception {
+        List<Event> rawEvents = ptRepository.getAllEventsForTrace(traceiri);
+        return enactmentController.getCleanedEventsForTimeline(rawEvents);
+    }
 
 	
 }

+ 125 - 20
src/main/java/ua/be/wee/model/EnactmentController.java

@@ -30,6 +30,7 @@ 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;
+import ua.be.wee.model.nodes.ports.DataOutputPort;
 import ua.be.wee.model.pm.PM;
 import ua.be.wee.model.pt.EndActivityEvent;
 import ua.be.wee.model.pt.Event;
@@ -93,14 +94,21 @@ public class EnactmentController {
 	public String getParentBranchId(String childBranchId) {
 	    return traceRepo.getParentBranchId(childBranchId);
 	}
+	
+	public String findSubTraceIriByInvokerEventIri(String invokerEventIri) {
+	    return traceRepo.findSubTraceIriByInvokerEventIri(invokerEventIri);
+	}
 
 	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);
-	    
+
+	    List<Event> events = pt.getEvents().stream()
+	        .filter(e -> branchId.equals(e.getBranchId()))
+	        .collect(Collectors.toList());
+
 	    Set<String> startedActivities = events.stream()
 	        .filter(e -> e instanceof StartActivityEvent)
 	        .map(e -> ((StartActivityEvent) e).getRelatesTo().getActivity().getIri())
@@ -143,7 +151,67 @@ public class EnactmentController {
 	    
 	    return generatePMTriggers(pm, filteredIris, branchId, invokingActivityMap.get(branchId));
 	}
+	
+	public List<TraceArtifact> propagateArtifactsForInvokingActivity(InvokingActivity invokingAct, PT pt) throws Exception {
+		String invokedPMIri = invokingAct.getInvokedPM();
+		PM subPm = this.getPM(invokedPMIri); 
+
+		List<DataOutputPort> subProcessParamPorts = nodeRepo.getParameterOutputPorts(subPm.getIri());
+
+		List<TraceArtifact> propagatedArtifacts = new ArrayList<>();
+
+		for (DataOutputPort invokerPort : invokingAct.getDataOutPorts()) {			
+			DataOutputPort matchedSubPort = subProcessParamPorts.stream()
+				.filter(p -> p.getName().equals(invokerPort.getName()))
+				.findFirst().orElse(null);
+
+			if (matchedSubPort != null) {				
+				Artifact sourceArtifactDef = nodeRepo.getSourceArtifactForParameterOutputPort(matchedSubPort.getIri());
 
+				if (sourceArtifactDef != null) {
+					TraceArtifact subProcessTraceArtifact = traceRepo.findLatestTraceArtifactForDefinition(sourceArtifactDef.getIri(), pt.getEvents());
+
+					if (subProcessTraceArtifact != null) {						
+						TraceArtifact propagatedArtifact = new TraceArtifact();
+						propagatedArtifact.setRelatesTo(invokerPort.getArtifact());
+						propagatedArtifact.setLocation(subProcessTraceArtifact.getLocation());
+						propagatedArtifacts.add(propagatedArtifact);
+
+					} else {
+					}
+				}
+			} else {
+			}
+		}
+
+		return propagatedArtifacts;
+	}
+	
+	public List<Event> getCleanedEventsForTimeline(List<Event> originalEvents) {
+        List<Event> cleanedEvents = new ArrayList<>();
+        for (Event originalEvent : originalEvents) {
+            if (originalEvent instanceof EndActivityEvent) {
+                EndActivityEvent endEvent = (EndActivityEvent) originalEvent;
+                if (endEvent.getRelatesTo() != null && endEvent.getRelatesTo().getActivity() instanceof InvokingActivity) {
+                    
+                    EndActivityEvent cleanedEvent = new EndActivityEvent();
+                    cleanedEvent.setIri(endEvent.getIri());
+                    cleanedEvent.setTimestamp(endEvent.getTimestamp());
+                    cleanedEvent.setRelatesTo(endEvent.getRelatesTo());
+                    cleanedEvent.setBranchId(endEvent.getBranchId());
+                    cleanedEvent.setProducedArtifacts(new ArrayList<>());
+                    
+                    cleanedEvents.add(cleanedEvent);
+                } else {
+                    cleanedEvents.add(originalEvent);
+                }
+            } else {
+                cleanedEvents.add(originalEvent);
+            }
+        }
+        return cleanedEvents;
+    }
+	
 	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) {
@@ -164,11 +232,9 @@ public class EnactmentController {
 	                trigger.setInvokingActivityName(invokingActivityName);
 	                triggers.add(trigger);
 	            }
-	        } else if (node instanceof FinalNode) {
-	            trigger.setNode((FinalNode) node);
-	            trigger.setBranchId(branchId);
-	            triggers.add(trigger);
 	        }
+//	        } else if (node instanceof FinalNode) {
+//	        }
 	    }
 	    return triggers;
 	}
@@ -186,7 +252,10 @@ public class EnactmentController {
 
 	
 	public List<PMTrigger> getRunningActivities(PT pt, PM pm, String branchId, Map<String, String> invokingActivityMap) throws Exception {
-	    List<Event> events = getEvents(pt.getIri(), branchId);
+	    List<Event> events = pt.getEvents().stream()
+	        .filter(e -> branchId.equals(e.getBranchId()))
+	        .collect(Collectors.toList());
+	    
 	    List<PMTrigger> runningActs = new ArrayList<>();
 
 	    for (Event event : events) {
@@ -198,21 +267,12 @@ public class EnactmentController {
 	                continue;
 	            }
 
+	            String expectedEndIri = startEvent.getIri().replace("start_activity", "end_activity");
 	            boolean ended = events.stream()
 	                    .filter(e -> e instanceof EndActivityEvent)
-	                    .map(e -> (EndActivityEvent) e)
-	                    .anyMatch(e -> e.getRelatesTo().getActivity().getIri().equals(activity.getIri()));
+	                    .anyMatch(e -> e.getIri().equals(expectedEndIri));
 
 	            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);
@@ -220,10 +280,54 @@ public class EnactmentController {
 	            }
 	        }
 	    }
-	    System.out.println("[getRunningActivities] Returning " + runningActs.size() + " triggers for branch: " + branchId);
+	    String runningActivityNames = runningActs.stream()
+	            .map(t -> t.getPort().getActivity().getName())
+	            .collect(Collectors.joining(", "));
 	    return runningActs;
 	}
+	
+	public List<PMTrigger> getEndableInvokingActivities(PT pt, PM pm, String branchId, Map<String, String> invokingActivityMap) throws Exception {
+	    List<Event> eventsInBranch = pt.getEvents().stream()
+	        .filter(e -> branchId.equals(e.getBranchId()))
+	        .collect(Collectors.toList());
+	    
+	    List<PMTrigger> endableActs = new ArrayList<>();
+	    List<Event> allEvents = pt.getEvents();
 
+	    for (Event event : eventsInBranch) {
+	        if (event instanceof StartActivityEvent) {
+	            StartActivityEvent startEvent = (StartActivityEvent) event;
+	            Activity activity = startEvent.getRelatesTo().getActivity();
+
+	            if (activity instanceof InvokingActivity) {
+	                String expectedEndIri = startEvent.getIri().replace("start_activity", "end_activity");
+	                boolean ended = allEvents.stream()
+	                        .filter(e -> e instanceof EndActivityEvent)
+	                        .anyMatch(e -> e.getIri().equals(expectedEndIri));
+
+	                if (!ended) {
+	                    String subTraceIri = findSubTraceIriByInvokerEventIri(startEvent.getIri());
+	                    if (subTraceIri != null) {
+	                        String subTraceEndIri = subTraceIri + "_end";
+	                        boolean subProcessFinished = allEvents.stream().anyMatch(e -> e.getIri().equals(subTraceEndIri));
+	                        
+	                        if (subProcessFinished) {
+	                            PMTrigger trigger = generatePMTrigger(pm, startEvent.getRelatesTo(), branchId, invokingActivityMap.get(branchId));
+	                            if (trigger != null) {
+	                                endableActs.add(trigger);
+	                            }
+	                        }
+	                    }
+	                }
+	            }
+	        }
+	    }
+	    String endableActivityNames = endableActs.stream()
+	            .map(t -> t.getPort().getActivity().getName())
+	            .collect(Collectors.joining(", "));
+	    return endableActs;
+	}
+	
 	public Event addEndTraceEvent(String iri, String previous, String pmIRI) throws Exception {
 		return traceRepo.createEndTraceEvent(iri,previous, pmIRI);
 		
@@ -234,7 +338,8 @@ public class EnactmentController {
 	}
 	
 	public List<Event> getEvents(String traceIri, String branchId) throws Exception {
-	    return traceRepo.getEvents(traceIri, branchId);
+	    List<Event> events = traceRepo.getEvents(traceIri, branchId);
+	    return events;
 	}
 	
 	public String getParentTraceIri(String traceIri) {

+ 60 - 55
src/main/java/ua/be/wee/model/ExecutionFlowManager.java

@@ -2,77 +2,82 @@ package ua.be.wee.model;
 
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 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<>();
+	private final Map<String, List<Token>> activeTokensMap = new HashMap<>();
+	
+	private final Map<String, Set<String>> joinSynchronizationMap = 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;
-	    }
+	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;
+	}
 
+	public List<Token> getActiveTokens(String traceIri) {
+		return activeTokensMap.getOrDefault(traceIri, new ArrayList<>());
+	}
 
-	    // Get active tokens for a trace
-	    public List<Token> getActiveTokens(String traceIri) {
-	        return activeTokensMap.getOrDefault(traceIri, new ArrayList<>());
-	    }
+	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);
+			}
+		}
+	}
 
-	    // 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);
-	    }
+	public void addToken(String traceIri, Token token) {
+		activeTokensMap.computeIfAbsent(traceIri, k -> new ArrayList<>()).add(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;
+	}
 
-	    // 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;
-	    }
+	public void updateTokenCurrentNode(Token token, Node nextNode) {
+		token.setCurrentNode(nextNode);
+	}
 
+	public void clearTokensForTrace(String traceIri) {
+		activeTokensMap.remove(traceIri);
+		joinSynchronizationMap.keySet().removeIf(key -> key.startsWith(traceIri + "_"));
+	}
+	
+	public synchronized boolean synchronizeJoin(String traceIri, String joinNodeIri, String arrivingInputIri, List<String> requiredInputs) {
+		String joinKey = traceIri + "_" + joinNodeIri;
+		
+		Set<String> arrivedInputs = joinSynchronizationMap.computeIfAbsent(joinKey, k -> new HashSet<>());
+		
+        if (arrivedInputs.contains(arrivingInputIri)) {
+            return false;
+        }
 
-	    // Update current node of a token
-	    public void updateTokenCurrentNode(Token token, Node nextNode) {
-	        token.setCurrentNode(nextNode);
-	    }
+		arrivedInputs.add(arrivingInputIri);
 
-	    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);
-	        }
-	    }
-}
+		if (arrivedInputs.containsAll(requiredInputs)) {
+			joinSynchronizationMap.remove(joinKey);
+			return true;
+		}
+		
+		return false;
+	}
+}

+ 8 - 1
src/main/java/ua/be/wee/model/nodes/InitialNode.java

@@ -9,6 +9,8 @@ public class InitialNode extends Node {
 	
 	@OneToOne(cascade = CascadeType.ALL)
 	private Node next;
+	
+	private String name;
 
 	public Node getNext() {
 		return next;
@@ -18,6 +20,11 @@ public class InitialNode extends Node {
 		this.next = next;
 	}
 	
-	
+	public String getName() {
+        return name;
+    }
 
+    public void setName(String name) {
+        this.name = name;
+    }
 }

+ 38 - 42
src/main/java/ua/be/wee/model/repository/FusekiWrapper.java

@@ -9,8 +9,6 @@ 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 {
 	
@@ -19,18 +17,12 @@ public class FusekiWrapper {
 	private RDFConnectionRemoteBuilder builder;
 	
 	private String serviceURI;
-	
+
+	private static final Symbol SYM_JSON_READER_LENIENT = Symbol.create("http://jena.apache.org/riot#jsonReaderLenient");
 
 	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() {
 		
@@ -50,43 +42,47 @@ public class FusekiWrapper {
 	}
 	
 	public ResultSet execQuery(String queryStr) {
-		if (!"".equals(serviceURI)) {
-			QueryExecution q = QueryExecutionHTTP.service(serviceURI, queryStr);
-			return q.execSelect();
+		if (serviceURI == null || serviceURI.isEmpty()) {
+			throw new NullPointerException("Service URI not specified");
 		}
-		throw new NullPointerException("Service URI not specified");
 		
+		QueryExecution qExec = QueryExecutionHTTP.service(serviceURI, queryStr);
+		
+		qExec.getContext().set(SYM_JSON_READER_LENIENT, true);
+		
+		return qExec.execSelect();
 	}
 	
 	public boolean updateQuery(String query) {
-		if (!"".equals(serviceURI)) {
-			try {
-				RDFConnectionFuseki conn = (RDFConnectionFuseki) builder.build();
-				UpdateRequest request = UpdateFactory.create();
-				request.add(query);
-				conn.update(request);
-				conn.close();
-				return true;
-			} catch (Exception e) {
-				e.printStackTrace();
-				return false;
-			}
-			
+		if (serviceURI == null || serviceURI.isEmpty()) {
+			throw new NullPointerException("Service URI not specified");
+		}
+		
+		try {
+			RDFConnectionFuseki conn = (RDFConnectionFuseki) builder.build();
+			UpdateRequest request = UpdateFactory.create();
+			request.add(query);
+			conn.update(request);
+			conn.close();
+			return true;
+		} catch (Exception e) {
+			e.printStackTrace();
+			return false;
 		}
-		throw new NullPointerException("Service URI not specified");
-
 	}
 	
 	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;
-	        }
+	    if (serviceURI == null || serviceURI.isEmpty()) {
+	        throw new NullPointerException("Service URI not specified");
+	    }
+		
+	    try (QueryExecution qExec = QueryExecutionHTTP.service(serviceURI, queryStr)) {
+			qExec.getContext().set(SYM_JSON_READER_LENIENT, true);
+	        return qExec.execAsk();
+	    } catch (Exception e) {
+	        e.printStackTrace();
+	        return false;
 	    }
-	    throw new NullPointerException("Service URI not specified");
 	}
 	
 	public boolean testEndpoint() {
@@ -94,13 +90,13 @@ public class FusekiWrapper {
 				+ "SELECT ?pm WHERE {\n"
 				+ "  ?pm a pm:Model .\n"
 				+ "}";
-		QueryExecutionHTTP service = QueryExecutionHTTP.service(serviceURI, query);
-		ResultSet execSelect;
-		try {
-			execSelect = service.execSelect();
+		
+		try (QueryExecutionHTTP service = QueryExecutionHTTP.service(serviceURI, query)) {
+			service.getContext().set(SYM_JSON_READER_LENIENT, true);
+			ResultSet execSelect = service.execSelect();
+			return execSelect.hasNext();
 		} catch (Exception e) {
 			return false;
 		}
-		return execSelect.hasNext();
 	}
 }

+ 101 - 46
src/main/java/ua/be/wee/model/repository/NodeRespository.java

@@ -28,8 +28,6 @@ public class NodeRespository {
 
 	@Autowired
 	private PMRepository pmRepo;
-	
-	//TODO Create getPMbyActivityIRI 
 
 	public List<Node> getNodes(String pmIri) throws Exception {
 		List<Node> nodes = new ArrayList<Node>();
@@ -80,7 +78,6 @@ public class NodeRespository {
 	        port.setIri(iri);
 	        port.setName(sol.get("?name").toString());
 
-	        // Set activity
 	        String activityIri = sol.get("?activity").toString();
 	        Activity act = getGenericActivityByIri(activityIri);
 
@@ -107,7 +104,6 @@ public class NodeRespository {
 	        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"))) {
@@ -119,8 +115,6 @@ public class NodeRespository {
 	    }
 	}
 
-
-
 	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"
@@ -137,7 +131,6 @@ public class NodeRespository {
 	        port.setIri(iri);
 	        port.setName(sol.get("?name").toString());
 
-	        // Set activity
 	        String activityIri = sol.get("?activity").toString();
 	        Activity act = getGenericActivityByIri(activityIri);
 
@@ -149,9 +142,7 @@ public class NodeRespository {
 	    }
 	}
 
-
 	private void updateControlNodesReferences(List<Node> nodes) {
-
 		for (Node node : nodes) {
 			if (node instanceof InitialNode) {
 				updateInitialNode(nodes, (InitialNode) node);
@@ -161,7 +152,6 @@ public class NodeRespository {
 				updateForkJoinNode(nodes, (ForkJoinNode) node);
 			}
 		}
-
 	}
 
 	private void updateForkJoinNode(List<Node> nodes, ForkJoinNode node) {
@@ -217,7 +207,6 @@ public class NodeRespository {
 			}
 		}
 		node.setNextNodes(list);
-
 	}
 
 	private void updateFinalNode(List<Node> nodes, FinalNode node) {
@@ -333,26 +322,62 @@ public class NodeRespository {
 			node = createInvokingActivity(iri.toString());
 			break;
 		case Node.INITIAL_IRI:
-			node = new InitialNode();
+			node = createInitialNode(iri.toString());
 			break;
 		case Node.FINAL_IRI:
-			node = new FinalNode();
+			node = createFinalNode(iri.toString());
 			break;
 		case Node.FORKJOIN_IRI:
-			// addlogic for fork join
 			node = new ForkJoinNode();
 			break;
 		case Node.ARTIFACT_IRI:
-			// addlogic for fork join
 			node = createArtifact(iri.toString());
 			break;
 		}
-		node.setIri(iri.toString());
+		if (node != null) {
+			node.setIri(iri.toString());
+		}
 		return node;
 	}
 
-	
+	private Node createInitialNode(String iri) throws Exception {
+		InitialNode node = new InitialNode();
+		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
+				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+				+ "SELECT ?name WHERE {\n"
+				+ "	?node owl:sameAs <" + iri + "> .\n"
+				+ " ?node pm:hasName ?name .\n"
+				+ "}";
+		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
+		if (results.hasNext()) {
+			QuerySolution soln = results.nextSolution();
+			RDFNode name = soln.get("?name");
+			node.setName(name.toString());
+			return node;
+		} else {
+			return node;
+		}
+	}
 
+	private Node createFinalNode(String iri) throws Exception {
+		FinalNode node = new FinalNode();
+		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
+				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+				+ "SELECT ?name WHERE {\n"
+				+ "	?node owl:sameAs <" + iri + "> .\n"
+				+ " ?node pm:hasName ?name .\n"
+				+ "}";
+		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
+		if (results.hasNext()) {
+			QuerySolution soln = results.nextSolution();
+			RDFNode name = soln.get("?name");
+			node.setName(name.toString());
+			return node;
+		} else {
+			return node;
+		}
+	}
+	
 	private Node createArtifact(String iri) throws Exception {
 		Artifact art = new Artifact();
 		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
@@ -372,7 +397,6 @@ public class NodeRespository {
 		} else {
 			throw new Exception("Query did not return any data: \n" + query );
 		}
-		
 	}
 
 	private Node createActivity(String iri) throws Exception {
@@ -391,14 +415,11 @@ public class NodeRespository {
 			act.setIri(iri);
 			act.setName(name.toString());
 			act.setTransformationName(type.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 );
 		}
@@ -464,13 +485,10 @@ public class NodeRespository {
 	        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));
@@ -481,7 +499,6 @@ public class NodeRespository {
 	    }
 	}
 
-
 	private List<DataOutputPort> getDataOutPorts(Activity act) {
 		List<DataOutputPort> list = new ArrayList<DataOutputPort>();
 		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
@@ -562,16 +579,13 @@ 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);
@@ -579,14 +593,10 @@ public class NodeRespository {
 	    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));
@@ -599,7 +609,6 @@ public class NodeRespository {
 	}
 
 	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"
@@ -618,8 +627,6 @@ public class NodeRespository {
 	            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);
 	}
 	
@@ -627,12 +634,12 @@ public class NodeRespository {
 	    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
+	            + "  <" + invokedActivityIri + "> pm:hasPort ?inputPort .\n"
+	            + "  ?inputPort a pm:CtrlInputPort ;\n"
+	            + "            pm:ctrlFrom ?outputPort .\n"
+	            + "  ?outputPort a pm:CtrlOutputPort ;\n"
+	            + "             pm:ofActivity ?activityIri .\n"
+	            + "  ?activityIri a pm:Activity .\n"
 	            + "} LIMIT 1";
 
 	    ResultSet results = FusekiWrapper.getInstance().execQuery(query);
@@ -640,11 +647,9 @@ public class NodeRespository {
 	        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;
 	}
 	
@@ -667,11 +672,9 @@ public class NodeRespository {
                 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" +
@@ -684,5 +687,57 @@ public class NodeRespository {
 		return null;
 	}
 	
+	public List<DataOutputPort> getParameterOutputPorts(String pmIri) {
+		List<DataOutputPort> ports = new ArrayList<>();
+		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
+				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+				+ "PREFIX ob: <http://ua.be/sdo2l/vocabulary/formalisms/object_diagram#>\n"
+				+ "SELECT ?port ?pname WHERE {\n"
+				+ "  ?pm a pm:Model ;\n"
+				+ "      owl:sameAs <" + pmIri + "> ;\n"
+				+ "      ob:hasObject ?port .\n"
+				+ "  ?port a pm:DataOutputPort ;\n"
+				+ "        pm:hasName ?pname .\n"
+				+ "  FILTER NOT EXISTS { ?port pm:ofActivity ?anyActivity . }\n"
+				+ "}";
+		
+		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
+		while (results.hasNext()) {
+			QuerySolution soln = results.nextSolution();
+			RDFNode portIri = soln.get("?port");
+			RDFNode portName = soln.get("?pname");
+			
+			DataOutputPort port = new DataOutputPort();
+			port.setIri(portIri.toString());
+			port.setName(portName.toString());
+			ports.add(port);
+		}
+		return ports;
+	}
+	
+	public Artifact getSourceArtifactForParameterOutputPort(String portIri) throws Exception {
+		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 ?artifact ?name ?typename WHERE {\n"
+				+ "  ?artifact pm:dataTo <" + portIri + "> .\n"
+				+ "  ?artifact a pm:Artifact ;\n"
+				+ "            pm:hasName ?name ;\n"
+				+ "            pm:hasType ?type .\n"
+				+ "  ?type base:hasGUID ?typename .\n"
+				+ "}";
 
-}
+		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
+		if (results.hasNext()) {
+			QuerySolution soln = results.nextSolution();
+			Artifact artifact = new Artifact();
+			artifact.setIri(soln.getResource("?artifact").getURI());
+			artifact.setName(soln.getLiteral("?name").getString());
+			artifact.setType(soln.getLiteral("?typename").getString());
+			return artifact;
+		} else {
+			return null;
+		}
+	}
+	
+}

+ 48 - 152
src/main/java/ua/be/wee/model/repository/PMRepository.java

@@ -9,18 +9,15 @@ 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;
 import ua.be.wee.model.util.Pair;
 
 @Repository
 public class PMRepository {
 
-	public List<PM> getAllPMs() {
+	public synchronized List<PM> getAllPMs() {
 		String query = "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
-				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base#>\n"
 				+ "PREFIX obj: <http://ua.be/sdo2l/vocabulary/formalisms/object_diagram#>\n"
 				+ "SELECT ?pm ?pmName WHERE {\n"
 				+ "  ?pm a pm:Model ;\n"
@@ -40,7 +37,7 @@ public class PMRepository {
 		return list;
 	}
 
-	public PM getPM(String pmiri) throws Exception {
+	public synchronized PM getPM(String pmiri) throws Exception {
 		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
 				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
 				+ "PREFIX obj: <http://ua.be/sdo2l/vocabulary/formalisms/object_diagram#>\n"
@@ -65,7 +62,7 @@ public class PMRepository {
 		
 	}
 
-	public List<Pair<String,String>> findNextNodes(String iri, String trace, ExecutionFlowManager executionFlowManager) throws Exception {
+	public synchronized 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"
@@ -82,21 +79,21 @@ public class PMRepository {
 	    while (results.hasNext()) {
 	        QuerySolution soln = results.nextSolution();
 	        RDFNode type = soln.get("?nodetype");
+	        RDFNode nextNode = soln.get("?e");
 	        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());
-	        
+	            pair = new Pair<String,String>(nextNode.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));
+	            if (isJoin(nextNode.toString())) {
+	                if (checkJoin(nextNode.toString(), trace, iri, executionFlowManager)) {
+	                    list.addAll(findNextNodes(nextNode.toString(), trace, executionFlowManager));
 	                }
-	            } else {
-	                list.addAll(findNextNodes(soln.get("?e").toString(), trace, executionFlowManager));
+	            } else { // It's a Fork
+	                list.addAll(findNextNodes(nextNode.toString(), trace, executionFlowManager));
 	            }
 	        } else {
-	            pair = new Pair<String,String>(soln.get("?e").toString(), null);
+	            pair = new Pair<String,String>(nextNode.toString(), null);
 	        }
 	        if (pair != null) {
 	            list.add(pair);
@@ -105,157 +102,56 @@ public class PMRepository {
 	    return list;
 	}
 
-	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"
-				+ "  ?sync a tr:JoinSync .\n"
-				+ "  ?sync tr:nodeIRI <" + node + "> .\n"
-				+ "  ?sync tr:trace <" + trace + "> .\n"
-				+ "}";
-		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
-		if (results.hasNext()) {
-			QuerySolution soln = results.nextSolution();
-			String sync = soln.get("?sync").toString();
-			return checkInputs(sync,node,trace,executionFlowManager, input);
-		} else {
-			createJoinSync(node,trace,input);
-			return false;
-		}
+	private synchronized boolean checkJoin(String node, String trace, String input, ExecutionFlowManager executionFlowManager) {
+		List<String> requiredInputs = getRequiredInputsForJoin(node);
+		return executionFlowManager.synchronizeJoin(trace, node, input, requiredInputs);
 	}
 
-	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"
-				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+	private synchronized List<String> getRequiredInputsForJoin(String joinNodeIri) {
+		String query = "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
 				+ "SELECT ?inCtrl WHERE {\n"
-				+ "  ?sync owl:sameAs  <" + sync + "> .\n"
-				+ "  ?sync tr:nodeIRI ?node .\n"
-				+ "  ?inCtrl pm:ctrlTo ?node .\n"
-				+ "  FILTER NOT EXISTS { ?sync tr:input ?inCtrl } \n"
+				+ "  ?inCtrl pm:ctrlTo <" + joinNodeIri + "> .\n"
 				+ "}";
 		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
-		while (results.hasNext()) {
-			QuerySolution soln = results.nextSolution();
-			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) {
-			insertRealizedInputInJoinSync(sync, input); 
-		} else if (list.isEmpty()) {
-			return true;
+		List<String> requiredInputs = new ArrayList<>();
+		while(results.hasNext()){
+			requiredInputs.add(results.nextSolution().getResource("?inCtrl").getURI());
 		}
-		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; 
+		return requiredInputs;
 	}
-
-	private void insertRealizedInputInJoinSync(String sync, String input) throws Exception {
-		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
-				+ "INSERT DATA {\n"
-				+ "GRAPH <"+ PT.TRACE_GRAPH_IRI + "> {"
-				+ "	<"+ sync + "> tr:input <" + input + "> .\n"
-				+ "	}"
-				+ "} ";
-		if (!FusekiWrapper.getInstance().updateQuery(query) ) {
-			throw new Exception("Error inserting data.");
+	
+	private synchronized boolean isJoin(String iri) {
+		String query = "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+				+ "SELECT ?input WHERE {\n"
+				+ "  ?input pm:ctrlTo <" + iri + "> .\n"
+				+ "}";
+		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
+		int count = 0;
+		while(results.hasNext()){
+			results.next();
+			count++;
 		}
+		return count > 1;
 	}
-
-	private void deleteJoinSync(String sync) throws Exception {
-		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
-				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
-				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
-				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/acyclic#>\n"
-				+ "DELETE { "
-				+ "GRAPH <"+ PT.TRACE_GRAPH_IRI + "> {"
-				+ "	?sync ?p ?v . }\n"
-				+ "}"
-				+ "WHERE { \n"
-				+ "  ?sync a tr:JoinSync .\n"
-				+ "  ?sync owl:sameAs <" + sync + "> .\n"
-				+ "  ?sync ?p ?v .\n"
-				+ "};\n";	
-		if (!FusekiWrapper.getInstance().updateQuery(query) ) {
-			throw new Exception("Error inserting data.");
-		}
+	
+	private boolean checkInputs(String sync, String node, String trace, ExecutionFlowManager executionFlowManager, String input) {
+		// Deprecated. Join logic is now handled by ExecutionFlowManager.
+		return false; 
 	}
 
-	private void createJoinSync(String node, String trace, String input) throws Exception {
-		String sync = trace + "_" + node.split("#")[1];
-		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
-				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
-				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
-				+ "INSERT DATA {\n"
-				+ "GRAPH <"+ PT.TRACE_GRAPH_IRI + "> {"
-				+ "	<"+ sync + "> rdf:type <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#JoinSync> , owl:Thing ;\n"
-				+ "    	tr:nodeIRI <" + node + "> ;\n"
-				+ " 	tr:trace <" + trace + "> ;\n"
-				+ " 	tr:input <" + input + "> ;\n"
-				+ "     owl:sameAs  <" + sync + "> .\n"
-				+ "	} \n"
-				+ "}; ";
-		if (!FusekiWrapper.getInstance().updateQuery(query) ) {
-			throw new Exception("Error inserting data.");
-		}
+	private void insertRealizedInputInJoinSync(String sync, String input) {
+		// Deprecated. Join logic is now handled by ExecutionFlowManager.
 	}
 
-	private boolean isJoin(String iri) {
-		String query = "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
-				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
-				+ "SELECT ?input WHERE {\n"
-				+ "  ?node owl:sameAs <" + iri + "> .\n"
-				+ "  ?input pm:ctrlTo ?node .\n"
-				+ "}";
-		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
-		results.next();
-		return results.hasNext();
+	private void deleteJoinSync(String sync) {
+		// Deprecated. Join logic is now handled by ExecutionFlowManager.
 	}
 
-	public void checkJoinSync(String portiri, String traceiri) 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"
-				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
-				+ "SELECT DISTINCT ?sync WHERE {\n"
-				+ "  ?inCtrl owl:sameAs <" + portiri + "> .\n"
-				+ "  {?node pm:ctrlTo ?inCtrl .}\n"
-				+ "  UNION\n"
-				+ "  {?inCtrl pm:ctrlFrom+ ?node . }\n"
-				+ "  ?node a pm:ForkJoin .\n"
-				+ "  ?sync tr:nodeIRI ?node .\n"
-				+ "  ?sync tr:trace <" + traceiri +"> .\n"
-				+ "  FILTER NOT EXISTS { \n"
-				+ "  	?in pm:ctrlTo ?node . \n"
-				+ "    FILTER NOT EXISTS {\n"
-				+ "   		?sync tr:input ?in .\n"
-				+ "    }\n"
-				+ "  }\n"
-				+ "}";
-		ResultSet results = FusekiWrapper.getInstance().execQuery(query);
-		while (results.hasNext()) {
-			QuerySolution soln = results.nextSolution();
-			String sync = soln.get("?sync").toString();
-			list.add(sync);
-		}
-		
-		for (String sync : list) {
-			deleteJoinSync(sync);
-		}
-		
+	private void createJoinSync(String node, String trace, String input) {
+		// Deprecated. Join logic is now handled by ExecutionFlowManager.
 	}
 	
-	
-}
+	public void checkJoinSync(String portiri, String traceiri) {
+		// Deprecated. Join logic is now handled by ExecutionFlowManager.
+	}
+}

+ 303 - 184
src/main/java/ua/be/wee/model/repository/PTRepository.java

@@ -15,7 +15,6 @@ import ua.be.wee.model.ExecutionFlowManager;
 import ua.be.wee.model.Token;
 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.InvokingActivity;
 import ua.be.wee.model.nodes.ports.ControlInputPort;
 import ua.be.wee.model.nodes.ports.ControlOutputPort;
@@ -28,7 +27,6 @@ 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.util.DateTimeConverter;
-import ua.be.wee.model.util.Pair;
 
 @Repository
 public class PTRepository {
@@ -43,7 +41,7 @@ public class PTRepository {
 	private ExecutionFlowManager executionFlowManager;
 
 
-	public PT createTrace(PM pm) throws Exception {
+	public synchronized PT createTrace(PM pm) throws Exception {
 		int index = getNextIndex(pm);
 		String traceiri = pm.getIri().split("#")[0] + "/traces#pt_" + index;
 		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
@@ -52,6 +50,7 @@ public class PTRepository {
 				+ "> {" + "	<" + traceiri
 				+ "> rdf:type <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#Event> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#StartTraceEvent> , owl:Thing , <http://ua.be/sdo2l/vocabulary/base/acyclic#element> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#element> ;\n"
 				+ "    	tr:relatesTo <" + pm.getIri() + "> ;\n" + " 	tr:hasTimestamp ?now ; \n"
+				+ "     tr:belongsToTopLevelTrace <" + traceiri + "> ; \n"
 				+ "     owl:sameAs  <" + traceiri + "> .\n" + "}" + "} WHERE {" + "	SELECT ?now\n" + "    WHERE\n"
 				+ "      {\n" + "      BIND(now() AS ?now) .\n" + "      }" + "}";
 		if (FusekiWrapper.getInstance().updateQuery(query)) {
@@ -83,7 +82,7 @@ public class PTRepository {
 
 	}
 
-	private int getNextIndex(PM pm) {
+	private synchronized int getNextIndex(PM pm) {
 		int index = 0;
 		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
 				+ "SELECT ?pt  WHERE {\n" + "	?pt a tr:StartTraceEvent ;\n" + "  		tr:relatesTo <" + pm.getIri()
@@ -96,7 +95,7 @@ public class PTRepository {
 		return index;
 	}
 
-	public String createStartTraceEvent(PT parentPt, PM pm, String eventIri, String parentTraceIri) throws Exception {
+	public synchronized String createStartTraceEvent(PT parentPt, PM pm, String eventIri, String parentTraceIri) throws Exception {
 	    int index = parentPt.getEvents().size();
 	    
 		String[] iriParts = parentPt.getIri().split("#");
@@ -114,6 +113,7 @@ public class PTRepository {
 	            + "    tr:hasTimestamp ?now ; \n"
 	            + "    tr:isPrecededBy <" + eventIri + "> ;\n" 
 	            + "    tr:relatesTo <" + pm.getIri() + "> ;\n" 
+	            + "    tr:belongsToTopLevelTrace <" + parentPt.getIri() + "> ; \n"
 	            + "    owl:sameAs <" + newTraceIri + "> .\n";
 
 	    if (parentTraceIri != null) {
@@ -152,17 +152,18 @@ public class PTRepository {
 	    return ev.getIri();
 	}
 	
-	public String getParentBranchId(String childBranchId) {
-	    String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n" +
-	                   "SELECT ?parent WHERE { <" + childBranchId + "> tr:parentBranch ?parent . }";
-	    ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
-	    if (rs.hasNext()) {
-	        return rs.nextSolution().getResource("?parent").getURI();
+	public synchronized String getParentBranchId(String childBranchId) {
+	    if (childBranchId == null || !childBranchId.contains("/")) {
+	        return null;
 	    }
-	    return null; // Return null if doesn't have a parent 
+	    int lastSlashIndex = childBranchId.lastIndexOf('/');
+	    if (lastSlashIndex > 0) {
+	        return childBranchId.substring(0, lastSlashIndex);
+	    }
+	    return null;
 	}
 
-	public String generateBranchId(String traceIri, String parentBranchId) {
+	public synchronized String generateBranchId(String traceIri, String parentBranchId) {
 	    String base = parentBranchId != null ? parentBranchId : traceIri + "/branches";
 	    String candidate = base + "/" + UUID.randomUUID().toString().substring(0, 8);
 
@@ -184,7 +185,7 @@ public class PTRepository {
 	}
 
 
-	public String createStartEvent(
+	public synchronized String createStartEvent(
 	        PT pt,
 	        ControlInputPort port,
 	        Activity act,
@@ -196,11 +197,8 @@ public class PTRepository {
 	    String branchId = isHierarchical
 	        ? (parentBranchId != null ? parentBranchId : generateBranchId(pt.getIri(), null))
 	        : parentBranchId;
-
-	    System.out.println("[createStartEvent] Checking if activity already started: " + act.getIri() + " in branch: " + branchId);
 		
 	    if (isActivityAlreadyStarted(pt, act.getIri(), branchId)) {
-	        System.err.println("[createStartEvent] Tentativa de iniciar atividade duplicada bloqueada: " + act.getName());
 	        return null; 
 	    }
 
@@ -210,10 +208,8 @@ public class PTRepository {
 
 	    if (rsPredecessor.hasNext()) {
 	        preiri = rsPredecessor.nextSolution().get("?predecessor").toString();
-	        System.out.println("[createStartEvent] Predecessor real encontrado no grafo: " + preiri);
 	    } else {
 	        preiri = pt.getIri();
-	        System.out.println("[createStartEvent] Nenhum predecessor encontrado, usando o trace como início: " + preiri);
 	    }
 	    
 	    int index = (int) pt.getEvents().stream()
@@ -228,8 +224,6 @@ public class PTRepository {
 	    
 		String portiri = port.getIri();
 
-	    System.out.println("[LOG] [createStartEvent] Creating StartActivityEvent with IRI: " + iri);
-
 	    StringBuilder query = new StringBuilder();
 	    query.append("PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n")
 	         .append("PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n")
@@ -242,6 +236,7 @@ public class PTRepository {
 	         .append(" tr:hasTimestamp ?now ;\n")
 	         .append(" tr:isPrecededBy <").append(preiri).append("> ;\n")
 	         .append(" tr:relatesTo <").append(portiri).append("> ;\n")
+	         .append(" tr:belongsToTopLevelTrace <").append(pt.getIri()).append("> ;\n")
 	         .append(" owl:sameAs <").append(iri).append("> .\n")
 	         .append("<").append(preiri).append("> tr:isFollowedBy <").append(iri).append("> .\n");
 
@@ -259,7 +254,6 @@ public class PTRepository {
 
 	    boolean success = FusekiWrapper.getInstance().updateQuery(query.toString());
 	    if (!success) {
-	        System.err.println("[ERROR] [createStartEvent] Failed to insert event " + iri);
 	        throw new Exception("Failed to insert event " + iri);
 	    }
 
@@ -268,8 +262,6 @@ public class PTRepository {
 	    ev.setIri(iri);
 	    ev.setRelatesTo(port);
 	    pt.addEvent(ev);
-	    System.out.println("[createStartEvent] StartActivityEvent added in memory for activity: " + act.getIri());
-
 	    String tsQuery = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
 	            + "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
 	            + "SELECT ?ts WHERE {\n"
@@ -297,7 +289,7 @@ public class PTRepository {
 	    return iri;
 	}
 	
-	public void updatePT(PT pt) throws Exception {
+	public synchronized void updatePT(PT pt) throws Exception {
 		String transitive = "";
 		for (Event ev : pt.getEvents()) {
 			if (ev instanceof StartTraceEvent) {
@@ -306,6 +298,7 @@ public class PTRepository {
 				transitive += "<" + ev.getIri() + ">,";
 			}
 		}
+		if (transitive.isEmpty()) return; // Avoid error on first event
 		transitive = transitive.substring(0, transitive.length() - 1);
 
 		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
@@ -328,7 +321,7 @@ public class PTRepository {
 
 	}
 	
-	public String getPmIriForTrace(String traceIri) {
+	public synchronized String getPmIriForTrace(String traceIri) {
 		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
 				+ "SELECT ?pm WHERE {\n"
 				+ "  <" + traceIri + "> a tr:StartTraceEvent ;\n"
@@ -341,101 +334,112 @@ public class PTRepository {
 		return null;
 	}
 	
-	public List<Event> getAllEventsForTrace(String topLevelTraceIri) throws Exception {
-		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
-				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
-				+ "SELECT DISTINCT ?event ?type ?timestamp ?port ?branchId WHERE {\n"
-				+ "  GRAPH <" + PT.TRACE_GRAPH_IRI + "> {\n"
-				+ "    ?trace a tr:StartTraceEvent .\n"
-				+ "    ?trace (tr:invokedByTrace)* <" + topLevelTraceIri + "> .\n"
-				+ "    BIND(REPLACE(STR(?trace), '#', '/') AS ?traceEventBase)\n"
-				+ "    ?event a ?type .\n"
-				+ "    FILTER(STRSTARTS(STR(?event), ?traceEventBase) || ?event = ?trace)\n"
-				+ "    ?event tr:hasTimestamp ?timestamp .\n"
-				+ "    FILTER(?type IN (tr:StartTraceEvent, tr:EndTraceEvent, tr:StartActivityEvent, tr:EndActivityEvent))\n"
-				+ "    OPTIONAL { ?event tr:relatesTo ?port . }\n"
-				+ "    OPTIONAL { ?event tr:belongsToBranch ?branchId . }\n"
-				+ "  }\n"
-				+ "} ORDER BY ?timestamp";
+	public synchronized List<Event> getAllEventsForTrace(String topLevelTraceIri) throws Exception {
+	    String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
+	            + "SELECT DISTINCT ?event ?type ?timestamp ?relatesTo ?branchId WHERE {\n"
+	            + "  GRAPH <" + PT.TRACE_GRAPH_IRI + "> {\n"
+	            + "    ?event tr:belongsToTopLevelTrace <" + topLevelTraceIri + "> .\n"
+	            + "    ?event a ?type ;\n"
+	            + "           tr:hasTimestamp ?timestamp .\n"
+	            + "    FILTER(?type IN (tr:StartTraceEvent, tr:EndTraceEvent, tr:StartActivityEvent, tr:EndActivityEvent))\n"
+	            + "    OPTIONAL { ?event tr:relatesTo ?relatesTo . }\n"
+	            + "    OPTIONAL { ?event tr:belongsToBranch ?branchId . }\n"
+	            + "  }\n"
+	            + "} ORDER BY ?timestamp";
 
-		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
-		List<Event> events = new ArrayList<>();
-		List<String> eventIris = new ArrayList<>(); 
+	    ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
+	    
+	    List<Event> events = new ArrayList<>();
+	    List<String> eventIris = new ArrayList<>(); 
 
-		while (rs.hasNext()) {
-			QuerySolution sol = rs.next();
-			String eventIRI = sol.get("?event").toString();
+	    while (rs != null && rs.hasNext()) {
+	        QuerySolution sol = rs.next();
+	        String eventIRI = sol.get("?event").toString();
 
-			if (eventIris.contains(eventIRI)) {
-				continue;
-			}
-			eventIris.add(eventIRI);
-
-			String type = sol.get("?type").toString();
-			String timestamp = sol.get("?timestamp").toString();
-			RDFNode portNode = sol.get("?port");
-			RDFNode branchNode = sol.get("?branchId");
-			String branchId = (branchNode != null) ? branchNode.toString() : null;
-
-			Event ev = null;
-			if (type.endsWith("StartTraceEvent")) {
-				ev = new StartTraceEvent();
-			} else if (type.endsWith("EndTraceEvent")) {
-				ev = new EndTraceEvent();
-			} else if (type.endsWith("StartActivityEvent")) {
-				StartActivityEvent startEv = new StartActivityEvent();
-				startEv.setBranchId(branchId);
-				if (portNode != null) {
-					ControlInputPort port = nodeRepo.getControlInputPort(portNode.toString());
-					startEv.setRelatesTo(port);
-				}
-				ev = startEv;
-			} else if (type.endsWith("EndActivityEvent")) {
-				EndActivityEvent endEv = new EndActivityEvent();
-				endEv.setBranchId(branchId);
-				if (portNode != null) {
-					ControlOutputPort port = nodeRepo.getControlOutputPort(portNode.toString());
-					endEv.setRelatesTo(port);
-				}
-				ev = endEv;
-			}
+	        if (eventIris.contains(eventIRI)) {
+	            continue;
+	        }
+	        eventIris.add(eventIRI);
 
-			if (ev != null) {
-				ev.setIri(eventIRI);
-				ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(timestamp));
-				events.add(ev);
-			}
-		}
-		return events;
+	        String type = sol.get("?type").toString();
+	        String timestamp = sol.get("?timestamp").toString();
+	        RDFNode relatesToNode = sol.get("?relatesTo");
+	        RDFNode branchNode = sol.get("?branchId");
+	        String branchId = (branchNode != null) ? branchNode.toString() : null;
+	        
+	        Event ev = null;
+
+	        if (type.endsWith("StartTraceEvent")) {
+	            StartTraceEvent startEv = new StartTraceEvent();
+	            if (relatesToNode != null) {
+	                startEv.setPmEnacted(pmRepo.getPM(relatesToNode.toString()));
+	            }
+	            ev = startEv;
+	        } else if (type.endsWith("EndTraceEvent")) {
+	            EndTraceEvent endEv = new EndTraceEvent();
+	            if (relatesToNode != null) {
+	                endEv.setPmEnacted(pmRepo.getPM(relatesToNode.toString()));
+	            }
+	            ev = endEv;
+	        } else if (type.endsWith("StartActivityEvent")) {
+	            StartActivityEvent startEv = new StartActivityEvent();
+	            startEv.setBranchId(branchId);
+	            if (relatesToNode != null) {
+	                startEv.setRelatesTo(nodeRepo.getControlInputPort(relatesToNode.toString()));
+	            }
+				startEv.setConsumedArtifacts(getConsumedArtifactsForEvent(eventIRI));
+	            ev = startEv;
+	        } else if (type.endsWith("EndActivityEvent")) {
+	            EndActivityEvent endEv = new EndActivityEvent();
+	            endEv.setBranchId(branchId);
+	            if (relatesToNode != null) {
+	                endEv.setRelatesTo(nodeRepo.getControlOutputPort(relatesToNode.toString()));
+	            }
+				endEv.setProducedArtifacts(getProducedArtifactsForEvent(eventIRI));
+	            ev = endEv;
+	        }
+
+	        if (ev != null) {
+	            ev.setIri(eventIRI);
+	            ev.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(timestamp));
+	            events.add(ev);
+	        }
+	    }
+	    return events;
 	}
 
-	public void createEndEvent(PT pt, List<TraceArtifact> arts, ControlOutputPort p, String branchId) throws Exception {
-	    System.out.println("initializing createEndEvent");
-	    System.out.println("Branch ID: " + branchId);
-	    System.out.println("Activity IRI: " + p.getActivity().getIri());
-	    System.out.println("Port IRI: " + p.getIri());
+	public synchronized void createEndEvent(PT pt, List<TraceArtifact> arts, ControlOutputPort p, String branchId) throws Exception {
+	    if (arts != null && !arts.isEmpty()) {
+	        for(TraceArtifact art : arts) {
+	        }
+	    } else {
+	    }
 
 	    List<Event> events = pt.getEvents();
-	    Event source = null;
+	    StartActivityEvent source = null;
 
-	    for (Event aux : events) {
+	    for (int i = events.size() - 1; i >= 0; i--) {
+	        Event aux = events.get(i);
 	        if (aux instanceof StartActivityEvent) {
 	            StartActivityEvent startEv = (StartActivityEvent) aux;
-	            boolean sameBranch = (branchId == null && startEv.getBranchId() == null)
-	                    || (branchId != null && branchId.equals(startEv.getBranchId()));
-	            boolean sameActivity = p.getActivity().getIri().equals(
-	                    startEv.getRelatesTo().getActivity().getIri());
+	            boolean sameBranch = (branchId != null && branchId.equals(startEv.getBranchId()));
+	            boolean sameActivity = p.getActivity().getIri().equals(startEv.getRelatesTo().getActivity().getIri());
 
 	            if (sameBranch && sameActivity) {
-	                source = startEv;
-	                System.out.println("StartActivityEvent found in memory: " + startEv.getIri());
-	                break;
+	                String expectedEndIri = startEv.getIri().replace("start_activity", "end_activity");
+	                boolean alreadyEnded = events.stream()
+	                                           .filter(e -> e instanceof EndActivityEvent)
+	                                           .anyMatch(e -> e.getIri().equals(expectedEndIri));
+	                
+	                if (!alreadyEnded) {
+	                    source = startEv;
+	                    break;
+	                }
 	            }
 	        }
 	    }
 
 	    if (source == null) {
-	        System.out.println("StartActivityEvent isn't in memory. Consulting RDF");
 
 	        String activityIRI = p.getActivity().getIri();
 	        String pmGraphIRI = activityIRI.split("#")[0];
@@ -452,16 +456,13 @@ public class PTRepository {
 	                        "  }\n" +
 	                        "}";
 
-	        System.out.println("[LOG] Query SPARQL:\n" + queryFindStartEvent);
-
 	        ResultSet rs = FusekiWrapper.getInstance().execQuery(queryFindStartEvent);
 	        if (rs.hasNext()) {
 	            String startEventIri = rs.next().getResource("?startEvent").getURI();
-	            System.out.println("[LOG] StartActivityEvent found on RDF: " + startEventIri);
 
 	            for (Event aux : pt.getEvents()) {
 	                if (aux.getIri().equals(startEventIri)) {
-	                    source = aux;
+	                    source = (StartActivityEvent) aux;
 	                    break;
 	                }
 	            }
@@ -469,19 +470,11 @@ public class PTRepository {
 	    }
 
 	    if (source == null) {
-	        System.out.println(" StartActivityEvent not found in memory or RDF");
-	        throw new Exception("StartActivityEvent not found in branch " + branchId);
+	        throw new Exception("StartActivityEvent not found for activity " + p.getActivity().getName() + " in branch " + branchId);
 	    }
 
 	    String preiri = pt.getLastEvent() != null ? pt.getLastEvent().getIri() : pt.getIri();
-	    String iri = source.getIri();
-	    String indexStr = iri.replaceAll("\\D+", "");
-	    int index = Integer.parseInt(indexStr);
-
-		String[] iriParts = pt.getIri().split("#");
-		String baseIri = iriParts[0];
-		String fragment = iriParts.length > 1 ? iriParts[1] : "";
-		String endiri = baseIri.replace("/traces", "/traces/" + fragment) + "#end_activity" + index;
+	    String endiri = source.getIri().replace("start_activity", "end_activity");
 
 	    List<String> artifactsIRI = new ArrayList<>();
 	    for (TraceArtifact art : arts) {
@@ -499,8 +492,9 @@ public class PTRepository {
 	            .append("    tr:isPrecededBy <").append(preiri).append("> ;\n")
 	            .append("    tr:relatesTo <").append(p.getIri()).append("> ;\n")
 	            .append("    tr:hasTimestamp ?now ;\n")
+	            .append("    tr:belongsToTopLevelTrace <").append(pt.getIri()).append("> ;\n")
 	            .append("    tr:belongsToBranch <").append(branchId).append("> ;\n")
-	            .append("    owl:sameAs <").append(endiri).append("> .\n"); // Removido ponto e vírgula extra
+	            .append("    owl:sameAs <").append(endiri).append("> .\n");
 
 	    for (String iris : artifactsIRI) {
 	        queryBuilder.append("    <").append(endiri).append("> tr:produces <").append(iris).append("> .\n");
@@ -549,7 +543,6 @@ public class PTRepository {
 	                    token.getCurrentNode().getIri().equals(p.getActivity().getIri()) &&
 	                    token.getBranchId().equals(branchId)) {
 
-	                    System.out.println("[DEBUG] [createEndEvent] Removing token for activity: " + token.getCurrentNode().getIri());
 	                    executionFlowManager.removeToken(pt.getIri(), token.getId());
 	                    break;
 	                }
@@ -561,10 +554,159 @@ public class PTRepository {
 	        throw new Exception("fail to insert EndActivityEvent on branch " + branchId);
 	    }
 	}
+	
+	private synchronized List<TraceArtifact> getConsumedArtifactsForEvent(String eventIri) {
+		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
+				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n"
+				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+				+ "SELECT DISTINCT ?art ?relatesTo ?artName ?artTypeName ?location ?tag ?guid WHERE {\n"
+				+ "  GRAPH <" + PT.TRACE_GRAPH_IRI + "> {\n"
+				+ "    <" + eventIri + "> tr:consumes ?art .\n"
+				+ "    ?art a tr:Artifact ;\n"
+				+ "         tr:relatesTo ?relatesTo ;\n"
+				+ "         tr:hasLocation ?location ;\n"
+				+ "         base:hasTag ?tag ;\n"
+				+ "         base:hasGUID ?guid .\n"
+				+ "  }\n"
+				+ "  OPTIONAL { ?relatesTo pm:hasName ?artName . }\n"
+				+ "  OPTIONAL { ?relatesTo pm:hasType ?typeResource . ?typeResource base:hasGUID ?artTypeName . }\n"
+				+ "}";
+		
+		return executeArtifactQuery(query);
+	}
 
+	private synchronized List<TraceArtifact> getProducedArtifactsForEvent(String eventIri) {
+		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
+				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n"
+				+ "PREFIX pm: <http://ua.be/sdo2l/vocabulary/formalisms/pm#>\n"
+				+ "SELECT DISTINCT ?art ?relatesTo ?artName ?artTypeName ?location ?tag ?guid WHERE {\n"
+				+ "  GRAPH <" + PT.TRACE_GRAPH_IRI + "> {\n"
+				+ "    <" + eventIri + "> tr:produces ?art .\n"
+				+ "    ?art a tr:Artifact ;\n"
+				+ "         tr:relatesTo ?relatesTo ;\n"
+				+ "         tr:hasLocation ?location ;\n"
+				+ "         base:hasTag ?tag ;\n"
+				+ "         base:hasGUID ?guid .\n"
+				+ "  }\n"
+				+ "  OPTIONAL { ?relatesTo pm:hasName ?artName . }\n"
+				+ "  OPTIONAL { ?relatesTo pm:hasType ?typeResource . ?typeResource base:hasGUID ?artTypeName . }\n"
+				+ "}";
+		
+		return executeArtifactQuery(query);
+	}
+
+	
+	private synchronized List<TraceArtifact> executeArtifactQuery(String query) {
+		List<TraceArtifact> artifacts = new ArrayList<>();
+		java.util.Set<String> seenGuids = new java.util.HashSet<>();
+		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
+		
+		while (rs.hasNext()) {
+			QuerySolution sol = rs.next();
+			
+			String guid = sol.getLiteral("?guid").getString();
+			if (seenGuids.contains(guid)) {
+				continue;
+			}
+			seenGuids.add(guid);
+			
+			TraceArtifact tArt = new TraceArtifact();
+			tArt.setIri(sol.getResource("?art").getURI());
+			tArt.setLocation(sol.getLiteral("?location").getString());
+			tArt.setTag(sol.getLiteral("?tag").getString());
+			tArt.setGUID(guid);
+			
+			Artifact artifactDefinition = new Artifact();
+			artifactDefinition.setIri(sol.getResource("?relatesTo").getURI());
+
+			if (sol.contains("?artName")) {
+				artifactDefinition.setName(sol.getLiteral("?artName").getString());
+			} else {
+                artifactDefinition.setName("Unknown Name");
+            }
+			
+			if (sol.contains("?artTypeName")) {
+				artifactDefinition.setType(sol.getLiteral("?artTypeName").getString());
+			}
+			
+			tArt.setRelatesTo(artifactDefinition);
+			artifacts.add(tArt);
+		}
+		return artifacts;
+	}
+	
+	private synchronized List<TraceArtifact> getArtifactsForEvent(String eventIri) {
+		List<TraceArtifact> artifacts = new ArrayList<>();
+		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
+				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n"
+				+ "SELECT ?art ?relatesTo ?location ?tag ?guid WHERE {\n"
+				+ "  { <" + eventIri + "> tr:produces ?art . } UNION { <" + eventIri + "> tr:consumes ?art . }\n"
+				+ "  ?art a tr:Artifact ;\n"
+				+ "       tr:relatesTo ?relatesTo ;\n"
+				+ "       tr:hasLocation ?location ;\n"
+				+ "       base:hasTag ?tag ;\n"
+				+ "       base:hasGUID ?guid .\n"
+				+ "}";
+
+		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
+		while (rs.hasNext()) {
+			QuerySolution sol = rs.next();
+			TraceArtifact tArt = new TraceArtifact();
+			tArt.setIri(sol.getResource("?art").getURI());
+			tArt.setLocation(sol.getLiteral("?location").getString());
+			tArt.setTag(sol.getLiteral("?tag").getString());
+			tArt.setGUID(sol.getLiteral("?guid").getString());
+			
+			Artifact relatedArtifactDef = new Artifact();
+			relatedArtifactDef.setIri(sol.getResource("?relatesTo").getURI());
+			try {
+				Artifact fullDef = (Artifact) nodeRepo.getNodes(relatedArtifactDef.getIri());
+				if(fullDef != null){
+					tArt.setRelatesTo(fullDef);
+				} else {
+					tArt.setRelatesTo(relatedArtifactDef);
+				}
+			} catch (Exception e) {
+				tArt.setRelatesTo(relatedArtifactDef);
+			}
+
+			artifacts.add(tArt);
+		}
+		return artifacts;
+	}
+	
+	public synchronized TraceArtifact findLatestTraceArtifactForDefinition(String artifactDefinitionIri, List<Event> allEvents) {
+		List<TraceArtifact> candidates = new ArrayList<>();
+		
+		for (Event event : allEvents) {
+			if (event instanceof EndActivityEvent) {
+				EndActivityEvent endEvent = (EndActivityEvent) event;
+				if (endEvent.getProducedArtifacts() != null) {
+					for (TraceArtifact produced : endEvent.getProducedArtifacts()) {
+						if (produced.getRelatesTo() != null && produced.getRelatesTo().getIri().equals(artifactDefinitionIri)) {
+							candidates.add(produced);
+						}
+					}
+				}
+			}
+		}
+
+		if (candidates.isEmpty()) {
+			return null;
+		}
+
+		TraceArtifact latest = candidates.stream()
+			.max(java.util.Comparator.comparing(TraceArtifact::getTimestamp))
+			.orElse(null);
+		
+		if (latest != null) {
+		}
+
+		return latest;
+	}
 
 
-	private String createTraceArtifact(String endEvIRI, String startEvIRI, TraceArtifact art, PT pt) throws Exception {
+	private synchronized String createTraceArtifact(String endEvIRI, String startEvIRI, TraceArtifact art, PT pt) throws Exception {
 
 		TraceArtifact latest = getLastestVersion(startEvIRI, art);
 		if (latest != null) {
@@ -643,11 +785,22 @@ public class PTRepository {
 		}
 	}
 	
-	public boolean isActivityAlreadyStarted(PT pt, String activityIri, String branchId) {
-	    System.out.println("[isActivityAlreadyStarted] Checking activity " + activityIri + " within branch " + branchId);
-	    
+	public synchronized String findSubTraceIriByInvokerEventIri(String invokerEventIri) {
+	    String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n" +
+	                   "SELECT ?subTrace WHERE { " +
+	                   "  ?subTrace a tr:StartTraceEvent ; " +
+	                   "            tr:isPrecededBy <" + invokerEventIri + "> . " +
+	                   "}";
+	    ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
+	    if (rs.hasNext()) {
+	        return rs.nextSolution().getResource("?subTrace").getURI();
+	    }
+	    return null;
+	}
+	
+	public synchronized boolean isActivityAlreadyStarted(PT pt, String activityIri, String branchId) {
 	    List<Event> branchEvents = pt.getEvents().stream()
-	        .filter(e -> e.getBranchId() != null && e.getBranchId().equals(branchId))
+	        .filter(e -> branchId.equals(e.getBranchId()))
 	        .collect(Collectors.toList());
 
 	    List<StartActivityEvent> startEventsForActivity = branchEvents.stream()
@@ -657,30 +810,24 @@ public class PTRepository {
 	        .collect(Collectors.toList());
 
 	    if (startEventsForActivity.isEmpty()) {
-	        System.out.println("[isActivityAlreadyStarted] Result: false (no start events found in this branch)");
-	        return false;
+	        return false; 
 	    }
 
 	    for (StartActivityEvent startEvent : startEventsForActivity) {
+	        String expectedEndIri = startEvent.getIri().replace("start_activity", "end_activity");
 	        boolean hasMatchingEndEvent = branchEvents.stream()
 	            .filter(e -> e instanceof EndActivityEvent)
-	            .map(e -> (EndActivityEvent) e)
-	            .anyMatch(ee -> {
-	                String expectedEndIri = startEvent.getIri().replace("start_activity", "end_activity");
-	                return ee.getIri().equals(expectedEndIri);
-	            });
+	            .anyMatch(ee -> ee.getIri().equals(expectedEndIri));
 	        
 	        if (!hasMatchingEndEvent) {
-	            System.out.println("[isActivityAlreadyStarted] Result: true (start event without matching end event found in this branch)");
 	            return true;
 	        }
 	    }
 
-	    System.out.println("[isActivityAlreadyStarted] Result: false (all start events have a matching end event in this branch)");
 	    return false;
 	}
 
-	private TraceArtifact getLastestVersion(String startEvIRI, TraceArtifact art) {
+	private synchronized TraceArtifact getLastestVersion(String startEvIRI, TraceArtifact art) {
 		String query = "PREFIX pt: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
 				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n" + "SELECT DISTINCT ?art ?version\n"
 				+ "WHERE { \n" + "  ?art a pt:Artifact .\n" + "  ?art pt:relatesTo <" + art.getRelatesTo().getIri()
@@ -704,16 +851,20 @@ public class PTRepository {
 		return null;
 	}
 
-	public Event createEndTraceEvent(String ptIRI, String previous, String pmIRI) throws Exception {
+	public synchronized Event createEndTraceEvent(String ptIRI, String previous, String pmIRI) throws Exception {
 		String traceiri = ptIRI + "_end";
 
+	    String topLevelTraceIri = findTopLevelTrace(ptIRI);
+
 		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
 				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
 				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n" + "INSERT {\n" + "GRAPH <" + PT.TRACE_GRAPH_IRI
 				+ "> {" + "	<" + traceiri
 				+ "> rdf:type <http://ua.be/sdo2l/vocabulary/base/acyclic#element> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#EndTraceEvent> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#Event> , <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#element> , owl:Thing ;\n"
 				+ " 	tr:isPrecededBy <" + previous + "> ;\n" + "    	tr:relatesTo <" + pmIRI + "> ;\n"
-				+ "     tr:hasTimestamp ?now ; \n" + "     owl:sameAs  <" + traceiri + "> .\n" + " <" + previous
+				+ "     tr:hasTimestamp ?now ; \n"
+				+ "     tr:belongsToTopLevelTrace <" + topLevelTraceIri + "> ; \n"
+				+ "     owl:sameAs  <" + traceiri + "> .\n" + " <" + previous
 				+ "> tr:isFollowedBy <" + traceiri + "> .\n" + "}" + "} WHERE {" + "	SELECT ?now\n" + "    WHERE\n"
 				+ "      {\n" + "      BIND(now() AS ?now) .\n" + "      }" + "}";
 
@@ -737,10 +888,22 @@ public class PTRepository {
 		} else {
 			throw new Exception("Error inserting data.");
 		}
+	}
 
+	private synchronized String findTopLevelTrace(String traceIri) {
+		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
+				+ "SELECT ?topLevelTrace WHERE { "
+				+ "  <" + traceIri + "> (tr:invokedByTrace)* ?topLevelTrace . "
+				+ "  FILTER NOT EXISTS { ?topLevelTrace tr:invokedByTrace ?anotherTrace . }"
+				+ "}";
+		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
+		if (rs.hasNext()) {
+			return rs.nextSolution().getResource("?topLevelTrace").getURI();
+		}
+		return traceIri;
 	}
 
-	public List<StartTraceEvent> getStartTraceEvents() throws Exception {
+	public synchronized List<StartTraceEvent> getStartTraceEvents() throws Exception {
 		List<StartTraceEvent> list = new ArrayList<StartTraceEvent>();
 		String query = "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n"
 				+ "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
@@ -762,7 +925,7 @@ public class PTRepository {
 		return list;
 	}
 
-	public List<Event> getEvents(String traceIri, String branchId) throws Exception {
+	public synchronized List<Event> getEvents(String traceIri, String branchId) throws Exception {
 	    String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
 	            + "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
 	            + "SELECT ?event ?type ?timestamp ?port WHERE {\n"
@@ -820,51 +983,7 @@ public class PTRepository {
 	    return events;
 	}
 
-	private List<TraceArtifact> getArtifacts(Event ev, PM pm, List<TraceArtifact> artifacts) {
-		List<TraceArtifact> arts = new ArrayList<TraceArtifact>();
-		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
-				+ "PREFIX base: <http://ua.be/sdo2l/vocabulary/base/base#>\n"
-				+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>\n"
-				+ "SELECT ?art ?ts ?guid ?location ?tag ?pmArt ?next WHERE {\n" + "	?art a tr:Artifact .\n"
-				+ (ev instanceof StartActivityEvent ? "  	<" + ev.getIri() + "> tr:consumes ?art .\n"
-						: "  	<" + ev.getIri() + "> tr:produces ?art .\n")
-				+ "    ?art tr:addedAt ?ts .\n" + "    ?art base:hasGUID ?guid .\n"
-				+ "    ?art tr:hasLocation ?location .\n" + "    ?art base:hasTag ?tag .\n"
-				+ "    ?art tr:relatesTo ?pmArt .\n" + "	   OPTIONAL {\n"
-				+ "    	?art base:nextVersionOf ?next \n" + "    }" + "}";
-		ResultSet rs = FusekiWrapper.getInstance().execQuery(query);
-		while (rs.hasNext()) {
-			QuerySolution next = rs.next();
-			RDFNode art = next.get("?art");
-			RDFNode guid = next.get("?guid");
-			RDFNode ts = next.get("?ts");
-			RDFNode location = next.get("?location");
-			RDFNode tag = next.get("?tag");
-			RDFNode pmArt = next.get("?pmArt");
-			RDFNode nextArt = next.get("?next");
-			TraceArtifact tArt = new TraceArtifact();
-			tArt.setIri(art.toString());
-			tArt.setTimestamp(DateTimeConverter.convertSPARQLDateTimeToTimestamp(ts.toString()));
-			tArt.setRelatesTo((Artifact) pm.getNode(pmArt.toString()));
-			tArt.setGUID(guid.toString());
-			tArt.setLocation(location.toString());
-			tArt.setTag(tag.toString());
-			arts.add(tArt);
-
-			if (nextArt != null && !nextArt.toString().equals("")) {
-				for (TraceArtifact traceArtifact : artifacts) {
-					if (traceArtifact.getIri().equals(nextArt.toString())) {
-						traceArtifact.setNextVersion(tArt);
-					}
-				}
-			}
-
-			artifacts.add(tArt);
-		}
-		return arts;
-	}
-
-	public List<StartTraceEvent> getActiveTraces(String pm) {
+	public synchronized List<StartTraceEvent> getActiveTraces(String pm) {
 		List<StartTraceEvent> list = new ArrayList<StartTraceEvent>();
 		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
 				+ "SELECT ?trace ?ts ?next WHERE {\n" + "  ?trace tr:hasTimestamp ?ts .\n" + "  ?trace tr:relatesTo <"
@@ -890,7 +1009,7 @@ public class PTRepository {
 		return list;
 	}
 
-	public List<StartTraceEvent> getConcludedTraces(String pmiri) {
+	public synchronized List<StartTraceEvent> getConcludedTraces(String pmiri) {
 		List<StartTraceEvent> list = new ArrayList<StartTraceEvent>();
 		String query = "PREFIX tr: <http://ua.be/sdo2l/vocabulary/formalisms/processtraces#>\n"
 				+ "SELECT ?trace ?ts ?next WHERE {\n" + "  ?trace a tr:StartTraceEvent .\n"

+ 14 - 0
src/main/java/ua/be/wee/service/FileStorageServiceImpl.java

@@ -11,6 +11,7 @@ import java.util.stream.Stream;
 
 import org.apache.http.HttpResponse;
 import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.methods.CloseableHttpResponse;
 import org.apache.http.client.methods.HttpGet;
 import org.apache.http.client.methods.HttpPut;
 import org.apache.http.entity.InputStreamEntity;
@@ -91,6 +92,19 @@ public class FileStorageServiceImpl implements FileStorageService {
 			throw new RuntimeException(e.getMessage());
 		}
 	}
+	
+	public CloseableHttpResponse loadAsHttpResponse(String filename) throws IOException {
+	    if (storageServiceURL == null) {
+	        init();
+	    }
+	    
+	    String url = getURL(filename);
+
+	    CloseableHttpClient httpclient = HttpClients.createDefault();
+	    HttpGet httpGet = new HttpGet(url);
+
+	    return httpclient.execute(httpGet);
+	}
 
 	@Override
 	public String load(String filename) {

+ 1 - 1
src/main/resources/application.properties

@@ -1,5 +1,5 @@
 server.port=8081
-base_url=http://localhost:8081/
+base_url=http://localhost:8081
 spring.application.name=wee
 endpoint=http://localhost:3030/SystemDesignOntology2Layers
 storageURL=http://localhost:5000

+ 210 - 84
src/main/resources/templates/enact.html

@@ -6,6 +6,8 @@
 </head>
 <body>
 <main>
+    <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">
@@ -16,98 +18,85 @@
                     </span>
                 </div>
 
-                <div th:if="${errorMessage}" class="notification is-danger">
+                <div th:if="${errorMessage}" class="notification is-danger mt-4">
                     <p th:text="${errorMessage}"></p>
                 </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 class="columns is-multiline">
+                        <div class="column is-12">
+                            <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="handleContextChange(this)" required>
+                                                <option value="">Select Process Model</option>
+                                                <option th:each="branch : ${activeBranches}"
+                                                        th:value="${branch['branchId']}"
+                                                        th:text="${branch['pmName'] + (branch['invokingHierarchy'] != null ? ' ( ' + branch['invokingHierarchy'] + ' )' : '')}"
+                                                        th:title="${branch['pmName'] + (branch['invokingHierarchy'] != null ? ' ( ' + branch['invokingHierarchy'] + ' )' : '')}"
+                                                        th:selected="${branch['branchId'] == session.currentBranchId}">
+                                                </option>
+                                            </select>
+                                        </div>
                                     </div>
                                 </div>
-                            </div>
-                            <div class="control">
-                                <button class="button is-primary" type="submit" id="start-activity-button">Start Activity</button>
-                            </div>
-                        </form>
-                    </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 class="column is-6" th:if="${#lists.size(availableActsBranches) > 0}">
+                            <form th:action="@{/startAct}" method="post" onsubmit="showButtonLoadingState(this)">
+                                <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 : ${acts}"
+                                                        th:value="${act.iri}"
+                                                        th:text="${act.port != null ? act.port.activity.name : 'End Process'}"
+                                                        th:title="${act.port != null ? act.port.activity.name : 'End Process'}">
+                                                </option>
+                                            </select>
+                                        </div>
                                     </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 class="control mt-2">
+                                    <button class="button is-primary" type="submit" id="start-activity-button">Start 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 class="column is-6" th:if="${#lists.size(runningActsBranches) > 0}">
+                            <form th:action="@{/endselect}" method="get" onsubmit="showButtonLoadingState(this)">
+                                <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 : ${endacts}"
+                                                        th:value="${endAct.activity.iri}"
+                                                        th:text="${endAct.activity.name}"
+                                                        th:title="${endAct.activity.name}">
+                                                </option>
+                                            </select>
+                                        </div>
                                     </div>
                                 </div>
-                            </article>
+                                <input type="hidden" name="branchId" th:value="${session.currentBranchId}"/>
+                                <div class="control mt-2">
+                                    <button class="button is-danger" type="submit">End Activity</button>
+                                </div>
+                            </form>
                         </div>
                     </div>
+
+                    <div id="artifacts-display-area" class="artifacts pt-6 is-hidden">
+                        </div>
+
                 </div>
             </div>
 
@@ -119,12 +108,70 @@
 
                 <div class="details">
                     <div class="timeline">
-                        <th:block th:each="ev : ${eventViews}">
-                            <div class="timeline-item">
+                        <th:block th:each="ev : ${timelineEvents}">
+                            <div th:if="${ev instanceof T(ua.be.wee.model.pt.StartTraceEvent)}" class="timeline-item">
+                                <div class="event-content">
+                                    <p>
+                                        <span>Process Started: </span>
+                                        <span class="accent">
+                                            <span th:if="${invokingNamesForSubTraces != null and invokingNamesForSubTraces.containsKey(ev.iri)}"
+                                                  th:text="${invokingNamesForSubTraces.get(ev.iri)} + ': '"></span>
+                                            <span th:text="${ev.pmEnacted.name}"></span>
+                                        </span>
+                                    </p>
+                                </div>
+                                <div class="arrow">↓</div>
+                            </div>
+
+                            <div th:if="${ev instanceof T(ua.be.wee.model.pt.StartActivityEvent)}" class="timeline-item">
+                                <th:block th:with="activity = ${ev.relatesTo.activity}">
+                                    <div th:unless="${activity instanceof T(ua.be.wee.model.nodes.InvokingActivity)}">
+                                        <div class="event-content" th:with="port = ${ev.relatesTo}">
+                                            <p>
+                                                <span>Activity Started: </span>
+                                                <span class="accent">
+                                                    <span th:if="${invokingNames != null and invokingNames.containsKey(ev.branchId)}"
+                                                          th:text="${invokingNames.get(ev.branchId)}"></span>
+                                                    <span th:text="${activity.name}"></span>
+                                                </span>
+                                            </p>
+                                            <p class="port-info" style="font-size: 0.9em; color: #a0a0a0;">
+                                                Port: <span class="accent" th:text="${port.name}"></span>
+                                            </p>
+                                        </div>
+                                        <div class="arrow">↓</div>
+                                    </div>
+                                </th:block>
+                            </div>
+
+                            <div th:if="${ev instanceof T(ua.be.wee.model.pt.EndActivityEvent)}" class="timeline-item">
+                                <th:block th:with="activity = ${ev.relatesTo.activity}">
+                                    <div class="event-content" th:with="port = ${ev.relatesTo}">
+                                        <p>
+                                            <span>Activity Ended: </span>
+                                            <span class="accent">
+                                                <span th:if="${invokingNames != null and invokingNames.containsKey(ev.branchId)}"
+                                                      th:text="${invokingNames.get(ev.branchId)}"></span>
+                                                <span th:text="${activity.name}"></span>
+                                            </span>
+                                        </p>
+                                        <p class="port-info" style="font-size: 0.9em; color: #a0a0a0;">
+                                            Port: <span class="accent" th:text="${port.name}"></span>
+                                        </p>
+                                    </div>
+                                    <div class="arrow">↓</div>
+                                </th:block>
+                            </div>
+
+                            <div th:if="${ev instanceof T(ua.be.wee.model.pt.EndTraceEvent)}" class="timeline-item">
                                 <div class="event-content">
                                     <p>
-                                        <strong th:text="${ev.label + ': '}"></strong>
-                                        <span class="accent" th:text="${ev.target}"></span>
+                                        <span>Process Finished: </span>
+                                        <span class="accent">
+                                            <span th:if="${invokingNamesForSubTraces != null and invokingNamesForSubTraces.containsKey(ev.iri)}"
+                                                  th:text="${invokingNamesForSubTraces.get(ev.iri)} + ': '"></span>
+                                            <span th:text="${ev.pmEnacted.name}"></span>
+                                        </span>
                                     </p>
                                 </div>
                                 <div class="arrow">↓</div>
@@ -137,14 +184,36 @@
     </section>
 
     <script th:inline="javascript">
+        function showButtonLoadingState(formElement) {
+            const button = formElement.querySelector('button[type="submit"]');
+            if (button) {
+                button.classList.add('is-loading');
+            }
+            document.querySelectorAll('button').forEach(btn => {
+                btn.disabled = true;
+            });
+        }
+
+        function handleContextChange(selectElement) {
+            const controlDiv = selectElement.closest('.select');
+            if (controlDiv) {
+                controlDiv.classList.add('is-loading');
+            }
+            document.querySelectorAll('button').forEach(button => {
+                button.disabled = true;
+            });
+            selectElement.form.submit();
+        }
+
         document.addEventListener('DOMContentLoaded', () => {
             const activitySelector = document.getElementById('activity-selector');
             const startActivityButton = document.getElementById('start-activity-button');
+            const artifactsDisplayArea = document.getElementById('artifacts-display-area');
 
             if (activitySelector && startActivityButton) {
+                
                 const updateButtonText = () => {
                     const selectedOption = activitySelector.options[activitySelector.selectedIndex];
-
                     if (selectedOption && selectedOption.text === 'End Process') {
                         startActivityButton.textContent = 'End PM';
                     } else {
@@ -152,8 +221,65 @@
                     }
                 };
 
-                activitySelector.addEventListener('change', updateButtonText);
+                const fetchAndDisplayArtifacts = () => {
+                    const selectedIri = activitySelector.value;
+                    if (!selectedIri) {
+                        artifactsDisplayArea.innerHTML = '';
+                        artifactsDisplayArea.classList.add('is-hidden');
+                        return;
+                    }
+
+                    artifactsDisplayArea.classList.remove('is-hidden');
+
+                    fetch(`/in-arts?iri=${encodeURIComponent(selectedIri)}`)
+                        .then(response => {
+                            if (!response.ok) throw new Error('Network response was not ok');
+                            return response.json();
+                        })
+                        .then(artifacts => {
+                            artifactsDisplayArea.innerHTML = '';
+                            if (artifacts && artifacts.length > 0) {
+                                const title = document.createElement('p');
+                                title.className = 'is-size-4 is-capitalized';
+                                title.textContent = 'Required Input Artifacts';
+                                artifactsDisplayArea.appendChild(title);
 
+                                artifacts.forEach(art => {
+                                    const artifactElement = `
+                                        <article class="media">
+                                            <figure class="media-left">
+												<p class="image is-48x48" style="display: flex; justify-content: center; align-items: center;">
+	                                                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960" fill="#e3e3e3">
+	                                                  <path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z"/>
+	                                                </svg>
+	                                            </p>
+                                            </figure>
+                                            <div class="media-content">
+                                                <div class="content">
+                                                    <p>
+                                                        <strong>${art.name}</strong>
+                                                        <small>${art.type || ''}</small>
+                                                        <br/>
+                                                        <a href="${art.downloadUrl}">Download</a>
+                                                    </p>
+                                                </div>
+                                            </div>
+                                        </article>
+                                    `;
+                                    artifactsDisplayArea.insertAdjacentHTML('beforeend', artifactElement);
+                                });
+                            } else {
+                                artifactsDisplayArea.classList.add('is-hidden');
+                            }
+                        })
+                        .catch(error => {
+                            console.error('Error fetching artifacts:', error);
+                            artifactsDisplayArea.innerHTML = '<p class="has-text-danger">Could not load artifacts.</p>';
+                        });
+                };
+
+                activitySelector.addEventListener('change', updateButtonText);
+                activitySelector.addEventListener('change', fetchAndDisplayArtifacts);
                 updateButtonText();
             }
         });

Разница между файлами не показана из-за своего большого размера
+ 87 - 20
src/main/resources/templates/enactEnd.html


+ 67 - 78
src/main/resources/templates/endEnactment.html

@@ -36,14 +36,8 @@
             border-radius: 50%;
             background-color: #7a7a7a;
             color: white;
-            font-size: 0.8rem;
+            font-size: 1.2rem;
         }
-        .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;
@@ -58,16 +52,6 @@
             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>
@@ -89,83 +73,88 @@
             <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)}">
+                            <div class="timeline-item">
+                                <span class="timeline-icon">↓</span>
+                                <div class="timeline-content">
+                                    <p class="heading" th:text="${ev.timestampF}"></p>
+                                    <p class="title is-5">Process Started</p>
+                                    <p class="subtitle">
+                                        <span th:if="${invokingNamesForSubTraces != null and invokingNamesForSubTraces.containsKey(ev.iri)}"
+                                              th:text="${invokingNamesForSubTraces.get(ev.iri)} + ': '"></span>
+                                        <span th:text="${ev.pmEnacted.name}"></span>
+                                    </p>
+                                </div>
+                            </div>
+                        </div>
 
-                                    <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 th:if="${ev instanceof T(ua.be.wee.model.pt.StartActivityEvent)}">
+                            <th:block th:with="activity = ${ev.relatesTo.activity}">
+                                <div th:unless="${activity instanceof T(ua.be.wee.model.nodes.InvokingActivity)}">
+                                    <div class="timeline-item">
+                                        <span class="timeline-icon">↓</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>
+                                            <p class="title is-5">Activity Started</p>
+                                            <p class="subtitle">
+                                                <span th:if="${invokingNames != null and invokingNames.containsKey(ev.branchId)}"
+                                                      th:text="${invokingNames.get(ev.branchId)}"></span>
+                                                <span th:text="${activity.name}"></span>
+                                            </p>
+                                            <small th:text="'Port: ' + ${ev.relatesTo.name}"></small>
                                         </div>
                                     </div>
+                                </div>
+                            </th:block>
+                        </div>
+                        
+                        <div th:if="${ev instanceof T(ua.be.wee.model.pt.EndActivityEvent)}">
+                            <th:block th:with="activity = ${ev.relatesTo.activity}">
+                                <div class="timeline-item">
+                                    <span class="timeline-icon">↓</span>
+                                    <div class="timeline-content">
+                                        <p class="heading" th:text="${ev.timestampF}"></p>
+                                        <p class="title is-5">Activity Ended</p>
+                                        <p class="subtitle">
+                                            <span th:if="${invokingNames != null and invokingNames.containsKey(ev.branchId)}"
+                                                  th:text="${invokingNames.get(ev.branchId)}"></span>
+                                            <span th:text="${activity.name}"></span>
+                                        </p>
+                                        <small th:text="'Port: ' + ${ev.relatesTo.name}"></small>
 
-                                    <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 class="mt-2" th:if="${not #lists.isEmpty(ev.producedArtifacts) and not (activity instanceof T(ua.be.wee.model.nodes.InvokingActivity))}">
+                                            <div class="tags">
+                                                <th:block th:each="art : ${ev.producedArtifacts}">
+                                                    <span class="tag is-light is-link" th:title="${art.location}">
+                                                        <strong>Artifact:</strong>&nbsp;[[${art.relatesTo.name}]]
+                                                        <span th:if="${art.relatesTo.type != null}"
+                                                              th:text="'(Type: ' + ${art.relatesTo.type} + ')'"
+                                                              class="ml-1 is-family-monospace is-size-7"></span>
+                                                    </span>
+                                                </th:block>
                                             </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 th:if="${ev instanceof T(ua.be.wee.model.pt.EndTraceEvent)}">
+                             <div class="timeline-item">
+                                <span class="timeline-icon">↓</span>
+                                <div class="timeline-content">
+                                    <p class="heading" th:text="${ev.timestampF}"></p>
+                                    <p class="title is-5">Process Finished</p>
+                                    <p class="subtitle">
+                                        <span th:if="${invokingNamesForSubTraces != null and invokingNamesForSubTraces.containsKey(ev.iri)}"
+                                              th:text="${invokingNamesForSubTraces.get(ev.iri)} + ': '"></span>
+                                        <span th:text="${ev.pmEnacted.name}"></span>
+                                    </p>
                                 </div>
                             </div>
-                        </th:block>
+                        </div>
+
                     </th:block>
                 </div>
             </div>

+ 1 - 1
src/main/resources/templates/fragments/automated.html

@@ -12,7 +12,7 @@
     </p>
   </a>
 
-  <form class="panel-block" th:action="@{/reload}" method="post">
+  <form class="panel-block" th:action="@{/reload}" method="get">
     <button class="button is-primary" type="submit"><img class="refresh" width="32" height="32" th:src="@{/img/refresh.svg}" alt="Circular arrow"></button>
   </form>
 </article>