From 13194ac56803218ae8cfe6eba42f6ae88d168494 Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 21 May 2026 14:14:01 +0200 Subject: [PATCH 01/30] temp --- .../CrossProfileDiagramRestController.java | 67 +++++++++++++++++++ .../crossProfileDiagram/MergedClassDTO.java | 41 ++++++++++++ .../rdfarchitect/database/DatabasePort.java | 9 +++ .../inmemory/GraphWithContextCollection.java | 4 ++ .../database/inmemory/InMemoryDatabase.java | 9 +++ .../inmemory/InMemoryDatabaseAdapter.java | 6 ++ .../inmemory/InMemoryDatabaseImpl.java | 6 ++ .../database/inmemory/SessionDataStore.java | 9 +++ .../inmemory/SessionDataStoreImpl.java | 12 ++++ .../diagrams/CrossProfileDiagram.java | 30 +++++++++ .../diagrams/MergedClassInDiagram.java | 33 +++++++++ .../diagrams/CustomDiagramService.java | 6 ++ .../diagrams/GetCustomDiagramsUseCase.java | 9 +++ 13 files changed, 241 insertions(+) create mode 100644 backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestController.java create mode 100644 backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java create mode 100644 backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/CrossProfileDiagram.java create mode 100644 backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/MergedClassInDiagram.java diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestController.java new file mode 100644 index 00000000..d1d05569 --- /dev/null +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestController.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.api.controller.datasets.diagrams; + +import io.swagger.v3.oas.annotations.Parameter; +import lombok.RequiredArgsConstructor; +import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; +import org.rdfarchitect.services.diagrams.GetCustomDiagramsUseCase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/datasets/{datasetName}/crossprofilediagram") +@RequiredArgsConstructor +public class CrossProfileDiagramRestController { + + private static final Logger logger = + LoggerFactory.getLogger(AllCustomDatasetDiagramsRESTController.class); + + private final GetCustomDiagramsUseCase getCustomDiagramsUseCase; + + @GetMapping + public CrossProfileDiagram getCustomDiagramList( + @Parameter(description = "The name/url of the inquirer.") + @RequestHeader( + value = HttpHeaders.ORIGIN, + required = false, + defaultValue = "unknown") + String originURL, + @Parameter(description = "The literal name of the dataset.") @PathVariable + String datasetName) { + logger.info( + "Received GET request: \"/api/datasets/{{}}/crossprofilediagram\" from \"{}\"", + datasetName, + originURL); + + var result = getCustomDiagramsUseCase.getCrossProfileDiagram(datasetName); + + logger.info( + "Sending response to GET request: \"/api/datasets/{{}}/crossprofilediagram\" from \"{}\"", + datasetName, + originURL); + return result; + } + +} diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java new file mode 100644 index 00000000..6d0a25eb --- /dev/null +++ b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.api.dto.crossProfileDiagram; + +import org.rdfarchitect.api.dto.SuperClassDTO; + +import java.util.List; +import java.util.UUID; + +public class MergedClassDTO { + + private UUID uuid; + + private String prefix; + + private String label; + + private SuperClassDTO superClass; + + private String comment; + + private List stereotypes; + + private UUID belongsToCategory; + +} diff --git a/backend/src/main/java/org/rdfarchitect/database/DatabasePort.java b/backend/src/main/java/org/rdfarchitect/database/DatabasePort.java index e3def0eb..4ababe36 100644 --- a/backend/src/main/java/org/rdfarchitect/database/DatabasePort.java +++ b/backend/src/main/java/org/rdfarchitect/database/DatabasePort.java @@ -20,6 +20,7 @@ import org.apache.jena.graph.Graph; import org.apache.jena.shared.PrefixMapping; import org.rdfarchitect.database.inmemory.GraphWithContext; +import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; @@ -45,6 +46,14 @@ public interface DatabasePort { */ Map getDatasetDiagrams(String datasetName); + /** + * Get the {@link CrossProfileDiagram} for a dataset. + * + * @param datasetName literal dataset name + * @return the cross profile diagram belonging to the dataset + */ + CrossProfileDiagram getCrossProfileDiagram(String datasetName); + /** * Get the {@link DiagramLayout} for all custom diagrams defined on a dataset * diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java index 6259d50f..aa7921ff 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java @@ -31,6 +31,7 @@ import org.apache.jena.sparql.graph.GraphFactory; import org.apache.jena.sparql.graph.PrefixMappingReadOnly; import org.rdfarchitect.config.GraphCompressionConfig; +import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.rdf.RDFUtils; import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; @@ -64,6 +65,9 @@ public class GraphWithContextCollection { @Getter private final ConcurrentMap customDiagrams = new ConcurrentHashMap<>(); + @Getter + private final CrossProfileDiagram crossProfileDiagram = new CrossProfileDiagram(UUID.randomUUID()); + @Getter private final DiagramLayout diagramLayout = new DiagramLayout(); // lock to prohibit dirty reads/writes diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabase.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabase.java index 7e6e997e..bcd6b9df 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabase.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabase.java @@ -24,6 +24,7 @@ import org.apache.jena.sparql.graph.PrefixMappingReadOnly; import org.rdfarchitect.database.DatabaseConnection; import org.rdfarchitect.database.GraphIdentifier; +import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.exception.database.DataAccessException; import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; @@ -77,6 +78,14 @@ public interface InMemoryDatabase { */ Map getDatasetDiagrams(String datasetName); + /** + * Get the {@link CrossProfileDiagram} for a dataset. + * + * @param datasetName literal dataset name + * @return the cross profile diagram belonging to the dataset + */ + CrossProfileDiagram getCrossProfileDiagram(String datasetName); + /** * Get the {@link DiagramLayout} for all custom diagrams defined on a dataset * diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseAdapter.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseAdapter.java index ef509b75..592a98d7 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseAdapter.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseAdapter.java @@ -27,6 +27,7 @@ import org.rdfarchitect.database.DatabaseConnection; import org.rdfarchitect.database.DatabasePort; import org.rdfarchitect.database.GraphIdentifier; +import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; @@ -51,6 +52,11 @@ public Map getDatasetDiagrams(String datasetName) { return database.getDatasetDiagrams(datasetName); } + @Override + public CrossProfileDiagram getCrossProfileDiagram(String datasetName) { + return database.getCrossProfileDiagram(datasetName); + } + @Override public DiagramLayout getDatasetDiagramLayout(String datasetName) { return database.getDatasetDiagramLayout(datasetName); diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseImpl.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseImpl.java index e43cde84..fe2d77bb 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseImpl.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseImpl.java @@ -24,6 +24,7 @@ import org.rdfarchitect.context.SessionContext; import org.rdfarchitect.database.DatabaseConnection; import org.rdfarchitect.database.GraphIdentifier; +import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; import org.rdfarchitect.rdf.graph.wrapper.GraphRewindableWithUUIDs; @@ -63,6 +64,11 @@ public Map getDatasetDiagrams(String datasetName) { return getOrCreateSessionDataStore().getDatasetDiagrams(datasetName); } + @Override + public CrossProfileDiagram getCrossProfileDiagram(String datasetName) { + return getOrCreateSessionDataStore().getCrossProfileDiagram(datasetName); + } + @Override public DiagramLayout getDatasetDiagramLayout(String datasetName) { return getOrCreateSessionDataStore().getDatasetDiagramLayout(datasetName); diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStore.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStore.java index 82b7c6d2..1362d94d 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStore.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStore.java @@ -27,6 +27,7 @@ import org.apache.jena.sparql.graph.PrefixMappingReadOnly; import org.rdfarchitect.database.DatabaseConnection; import org.rdfarchitect.database.GraphIdentifier; +import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.exception.database.DataAccessException; import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; @@ -83,6 +84,14 @@ static Dataset wrapGraphInDataset(Graph graph, String graphUri) { */ Map getDatasetDiagrams(String datasetName); + /** + * Get the {@link CrossProfileDiagram} for a dataset. + * + * @param datasetName literal dataset name + * @return the cross profile diagram belonging to the dataset + */ + CrossProfileDiagram getCrossProfileDiagram(String datasetName); + /** * Get the {@link DiagramLayout} for all custom diagrams defined on a dataset * diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStoreImpl.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStoreImpl.java index f5911476..fc9ace93 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStoreImpl.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStoreImpl.java @@ -31,6 +31,7 @@ import org.apache.jena.sparql.graph.PrefixMappingReadOnly; import org.rdfarchitect.database.DatabaseConnection; import org.rdfarchitect.database.GraphIdentifier; +import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.exception.database.DataAccessException; import org.rdfarchitect.models.cim.queries.select.CIMBaseQueryBuilder; @@ -129,6 +130,17 @@ public Map getDatasetDiagrams(String datasetName) { } } + @Override + public CrossProfileDiagram getCrossProfileDiagram(String datasetName) { + lock.lock(); + try { + assertThatDatasetExists(datasetName); + return graphCollections.get(datasetName).getCrossProfileDiagram(); + } finally { + lock.unlock(); + } + } + @Override public DiagramLayout getDatasetDiagramLayout(String datasetName) { lock.lock(); diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/CrossProfileDiagram.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/CrossProfileDiagram.java new file mode 100644 index 00000000..f7fa9487 --- /dev/null +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/CrossProfileDiagram.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.database.inmemory.diagrams; + +import lombok.Data; +import java.util.List; +import java.util.UUID; + +@Data +public class CrossProfileDiagram { + + private final UUID diagramId; + private List classes; + +} diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/MergedClassInDiagram.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/MergedClassInDiagram.java new file mode 100644 index 00000000..66a635ff --- /dev/null +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/MergedClassInDiagram.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.database.inmemory.diagrams; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.rdfarchitect.models.cim.data.dto.relations.uri.URI; +import java.util.List; +import java.util.UUID; + +@Data +@AllArgsConstructor +public class MergedClassInDiagram { + + private UUID uuid; + private List graphUris; + +} diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java index 2fd1d70d..a5badf04 100644 --- a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java @@ -22,6 +22,7 @@ import org.rdfarchitect.database.DatabasePort; import org.rdfarchitect.database.GraphIdentifier; import org.rdfarchitect.database.inmemory.diagrams.ClassInDiagram; +import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.springframework.stereotype.Service; @@ -55,6 +56,11 @@ public List getCustomDiagramsForDataset(String datasetName) { return databasePort.getDatasetDiagrams(datasetName).values().stream().toList(); } + @Override + public CrossProfileDiagram getCrossProfileDiagram(String datasetName) { + return databasePort.getCrossProfileDiagram(datasetName); + } + @Override public void deleteCustomDiagram(String datasetName, String diagramId) { var diagrams = databasePort.getDatasetDiagrams(datasetName); diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/GetCustomDiagramsUseCase.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/GetCustomDiagramsUseCase.java index 21c9f02b..89d1d746 100644 --- a/backend/src/main/java/org/rdfarchitect/services/diagrams/GetCustomDiagramsUseCase.java +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/GetCustomDiagramsUseCase.java @@ -18,6 +18,7 @@ package org.rdfarchitect.services.diagrams; import org.rdfarchitect.database.GraphIdentifier; +import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import java.util.List; @@ -39,4 +40,12 @@ public interface GetCustomDiagramsUseCase { * @return The custom diagrams for the dataset. */ List getCustomDiagramsForDataset(String datasetName); + + /** + * Returns the cross profile diagram for the dataset. + * + * @param datasetName The name of the dataset. + * @return The cross profile diagram for the dataset. + */ + CrossProfileDiagram getCrossProfileDiagram(String datasetName); } From 19b638d7e015db5efdc3fe438acb8a83ff00103f Mon Sep 17 00:00:00 2001 From: Philipp Date: Tue, 26 May 2026 10:16:49 +0200 Subject: [PATCH 02/30] temp --- .../CrossProfileDiagramRestController.java | 6 +- .../crossProfileDiagram/ClassSourceDTO.java | 29 ++++ .../CrossProfileDiagramDTO.java} | 11 +- .../crossProfileDiagram/GraphSourcedDTO.java | 28 +++ .../crossProfileDiagram/MergedClassDTO.java | 28 ++- .../rdfarchitect/database/DatabasePort.java | 13 +- .../inmemory/GraphWithContextCollection.java | 7 +- .../database/inmemory/InMemoryDatabase.java | 13 +- .../inmemory/InMemoryDatabaseAdapter.java | 9 +- .../inmemory/InMemoryDatabaseImpl.java | 9 +- .../database/inmemory/SessionDataStore.java | 13 +- .../inmemory/SessionDataStoreImpl.java | 9 +- ...sProfileDiagram.java => GraphSourced.java} | 11 +- .../rdf/graph/wrapper/DiagramLayout.java | 24 ++- .../diagrams/CustomDiagramService.java | 86 +++++++++- .../diagrams/GetCustomDiagramsUseCase.java | 7 +- .../dl/select/QueryDiagramLayoutService.java | 6 +- .../dl/update/UpdateDiagramLayoutService.java | 6 +- .../classlayout/UpdateClassLayoutService.java | 4 +- .../services/select/GetClassListUseCase.java | 8 + .../services/select/QueryGraphService.java | 26 +++ frontend/src/lib/api/backend.js | 10 ++ .../svelteflow/CrossProfileDiagram.svelte | 159 ++++++++++++++++++ .../components/crossProfileDiagramUtils.js | 91 ++++++++++ .../svelteflow/svelteFlowWrapper.svelte | 1 + frontend/src/lib/sharedState.svelte.js | 1 + .../classEditor/mergedClassEditor.svelte | 71 ++++++++ .../packageNavigation/DatasetSection.svelte | 36 +++- .../src/routes/mainpage/packageWindow.svelte | 69 +++++--- 29 files changed, 675 insertions(+), 116 deletions(-) create mode 100644 backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java rename backend/src/main/java/org/rdfarchitect/{database/inmemory/diagrams/MergedClassInDiagram.java => api/dto/crossProfileDiagram/CrossProfileDiagramDTO.java} (78%) create mode 100644 backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/GraphSourcedDTO.java rename backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/{CrossProfileDiagram.java => GraphSourced.java} (83%) create mode 100644 frontend/src/lib/rendering/svelteflow/CrossProfileDiagram.svelte create mode 100644 frontend/src/lib/rendering/svelteflow/components/crossProfileDiagramUtils.js create mode 100644 frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestController.java index d1d05569..58f3f390 100644 --- a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestController.java +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestController.java @@ -19,7 +19,7 @@ import io.swagger.v3.oas.annotations.Parameter; import lombok.RequiredArgsConstructor; -import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; +import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramDTO; import org.rdfarchitect.services.diagrams.GetCustomDiagramsUseCase; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,12 +36,12 @@ public class CrossProfileDiagramRestController { private static final Logger logger = - LoggerFactory.getLogger(AllCustomDatasetDiagramsRESTController.class); + LoggerFactory.getLogger(CrossProfileDiagramRestController.class); private final GetCustomDiagramsUseCase getCustomDiagramsUseCase; @GetMapping - public CrossProfileDiagram getCustomDiagramList( + public CrossProfileDiagramDTO getCustomDiagramList( @Parameter(description = "The name/url of the inquirer.") @RequestHeader( value = HttpHeaders.ORIGIN, diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java new file mode 100644 index 00000000..757f925b --- /dev/null +++ b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.api.dto.crossProfileDiagram; + +import lombok.AllArgsConstructor; +import lombok.Data; +import java.util.UUID; + +@Data +@AllArgsConstructor +public class ClassSourceDTO { + private UUID classUuid; + private String graphUri; +} diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/MergedClassInDiagram.java b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/CrossProfileDiagramDTO.java similarity index 78% rename from backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/MergedClassInDiagram.java rename to backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/CrossProfileDiagramDTO.java index 66a635ff..412e16bd 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/MergedClassInDiagram.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/CrossProfileDiagramDTO.java @@ -15,19 +15,16 @@ * */ -package org.rdfarchitect.database.inmemory.diagrams; +package org.rdfarchitect.api.dto.crossProfileDiagram; import lombok.AllArgsConstructor; import lombok.Data; -import org.rdfarchitect.models.cim.data.dto.relations.uri.URI; import java.util.List; import java.util.UUID; @Data @AllArgsConstructor -public class MergedClassInDiagram { - - private UUID uuid; - private List graphUris; - +public class CrossProfileDiagramDTO { + private UUID diagramId; + private List classes; } diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/GraphSourcedDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/GraphSourcedDTO.java new file mode 100644 index 00000000..beec6f25 --- /dev/null +++ b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/GraphSourcedDTO.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.api.dto.crossProfileDiagram; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class GraphSourcedDTO { + private String graphUri; + private T value; +} diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java index 6d0a25eb..352d0808 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java @@ -17,25 +17,23 @@ package org.rdfarchitect.api.dto.crossProfileDiagram; -import org.rdfarchitect.api.dto.SuperClassDTO; - import java.util.List; import java.util.UUID; - +import lombok.AllArgsConstructor; +import lombok.Data; +import org.rdfarchitect.api.dto.association.AssociationPairDTO; +import org.rdfarchitect.api.dto.attributes.AttributeDTO; +import org.rdfarchitect.api.dto.enumentries.EnumEntryDTO; + +@Data +@AllArgsConstructor public class MergedClassDTO { private UUID uuid; - - private String prefix; - - private String label; - - private SuperClassDTO superClass; - - private String comment; - - private List stereotypes; - - private UUID belongsToCategory; + private String classUri; + private List sources; + private List> attributes; + private List> enumEntries; + private List> associationPairs; } diff --git a/backend/src/main/java/org/rdfarchitect/database/DatabasePort.java b/backend/src/main/java/org/rdfarchitect/database/DatabasePort.java index 4ababe36..238bb337 100644 --- a/backend/src/main/java/org/rdfarchitect/database/DatabasePort.java +++ b/backend/src/main/java/org/rdfarchitect/database/DatabasePort.java @@ -20,7 +20,6 @@ import org.apache.jena.graph.Graph; import org.apache.jena.shared.PrefixMapping; import org.rdfarchitect.database.inmemory.GraphWithContext; -import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; @@ -47,20 +46,20 @@ public interface DatabasePort { Map getDatasetDiagrams(String datasetName); /** - * Get the {@link CrossProfileDiagram} for a dataset. + * Get the {@link DiagramLayout} for all custom diagrams defined on a dataset * * @param datasetName literal dataset name - * @return the cross profile diagram belonging to the dataset + * @return diagram layout for the dataset */ - CrossProfileDiagram getCrossProfileDiagram(String datasetName); + DiagramLayout getDatasetDiagramLayout(String datasetName); /** - * Get the {@link DiagramLayout} for all custom diagrams defined on a dataset + * Returns the fixed UUID of the CrossProfileDiagram for the given dataset. * * @param datasetName literal dataset name - * @return diagram layout for the dataset + * @return UUID of the CrossProfileDiagram for the dataset */ - DiagramLayout getDatasetDiagramLayout(String datasetName); + UUID getCrossProfileDiagramUUID(String datasetName); /** * Loads the namespace prefix mapping for the dataset. diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java index aa7921ff..53a74bc0 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java @@ -31,7 +31,6 @@ import org.apache.jena.sparql.graph.GraphFactory; import org.apache.jena.sparql.graph.PrefixMappingReadOnly; import org.rdfarchitect.config.GraphCompressionConfig; -import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.rdf.RDFUtils; import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; @@ -65,11 +64,11 @@ public class GraphWithContextCollection { @Getter private final ConcurrentMap customDiagrams = new ConcurrentHashMap<>(); - @Getter - private final CrossProfileDiagram crossProfileDiagram = new CrossProfileDiagram(UUID.randomUUID()); - @Getter private final DiagramLayout diagramLayout = new DiagramLayout(); + @Getter + private final UUID crossProfileDiagramUUID = UUID.randomUUID(); + // lock to prohibit dirty reads/writes private final ReentrantLock lock = new ReentrantLock(); diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabase.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabase.java index bcd6b9df..21188d9d 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabase.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabase.java @@ -24,7 +24,6 @@ import org.apache.jena.sparql.graph.PrefixMappingReadOnly; import org.rdfarchitect.database.DatabaseConnection; import org.rdfarchitect.database.GraphIdentifier; -import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.exception.database.DataAccessException; import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; @@ -79,20 +78,20 @@ public interface InMemoryDatabase { Map getDatasetDiagrams(String datasetName); /** - * Get the {@link CrossProfileDiagram} for a dataset. + * Get the {@link DiagramLayout} for all custom diagrams defined on a dataset * * @param datasetName literal dataset name - * @return the cross profile diagram belonging to the dataset + * @return diagram layout for the dataset */ - CrossProfileDiagram getCrossProfileDiagram(String datasetName); + DiagramLayout getDatasetDiagramLayout(String datasetName); /** - * Get the {@link DiagramLayout} for all custom diagrams defined on a dataset + * Returns the fixed UUID of the CrossProfileDiagram for the given dataset. * * @param datasetName literal dataset name - * @return diagram layout for the dataset + * @return UUID of the CrossProfileDiagram for the dataset */ - DiagramLayout getDatasetDiagramLayout(String datasetName); + UUID getCrossProfileDiagramUUID(String datasetName); /** * Creates a new {@link GraphRewindableWithUUIDs} in a specified dataset. If the dataset does diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseAdapter.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseAdapter.java index 592a98d7..4201b0fc 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseAdapter.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseAdapter.java @@ -27,7 +27,6 @@ import org.rdfarchitect.database.DatabaseConnection; import org.rdfarchitect.database.DatabasePort; import org.rdfarchitect.database.GraphIdentifier; -import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; @@ -53,13 +52,13 @@ public Map getDatasetDiagrams(String datasetName) { } @Override - public CrossProfileDiagram getCrossProfileDiagram(String datasetName) { - return database.getCrossProfileDiagram(datasetName); + public DiagramLayout getDatasetDiagramLayout(String datasetName) { + return database.getDatasetDiagramLayout(datasetName); } @Override - public DiagramLayout getDatasetDiagramLayout(String datasetName) { - return database.getDatasetDiagramLayout(datasetName); + public UUID getCrossProfileDiagramUUID(String datasetName) { + return database.getCrossProfileDiagramUUID(datasetName); } @Override diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseImpl.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseImpl.java index fe2d77bb..d8d9586c 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseImpl.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseImpl.java @@ -24,7 +24,6 @@ import org.rdfarchitect.context.SessionContext; import org.rdfarchitect.database.DatabaseConnection; import org.rdfarchitect.database.GraphIdentifier; -import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; import org.rdfarchitect.rdf.graph.wrapper.GraphRewindableWithUUIDs; @@ -65,13 +64,13 @@ public Map getDatasetDiagrams(String datasetName) { } @Override - public CrossProfileDiagram getCrossProfileDiagram(String datasetName) { - return getOrCreateSessionDataStore().getCrossProfileDiagram(datasetName); + public DiagramLayout getDatasetDiagramLayout(String datasetName) { + return getOrCreateSessionDataStore().getDatasetDiagramLayout(datasetName); } @Override - public DiagramLayout getDatasetDiagramLayout(String datasetName) { - return getOrCreateSessionDataStore().getDatasetDiagramLayout(datasetName); + public UUID getCrossProfileDiagramUUID(String datasetName) { + return getOrCreateSessionDataStore().getCrossProfileDiagramUUID(datasetName); } @Override diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStore.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStore.java index 1362d94d..d5483fe7 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStore.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStore.java @@ -27,7 +27,6 @@ import org.apache.jena.sparql.graph.PrefixMappingReadOnly; import org.rdfarchitect.database.DatabaseConnection; import org.rdfarchitect.database.GraphIdentifier; -import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.exception.database.DataAccessException; import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; @@ -85,20 +84,20 @@ static Dataset wrapGraphInDataset(Graph graph, String graphUri) { Map getDatasetDiagrams(String datasetName); /** - * Get the {@link CrossProfileDiagram} for a dataset. + * Get the {@link DiagramLayout} for all custom diagrams defined on a dataset * * @param datasetName literal dataset name - * @return the cross profile diagram belonging to the dataset + * @return diagram layout for the dataset */ - CrossProfileDiagram getCrossProfileDiagram(String datasetName); + DiagramLayout getDatasetDiagramLayout(String datasetName); /** - * Get the {@link DiagramLayout} for all custom diagrams defined on a dataset + * Returns the fixed UUID of the CrossProfileDiagram for the given dataset. * * @param datasetName literal dataset name - * @return diagram layout for the dataset + * @return UUID of the CrossProfileDiagram for the dataset */ - DiagramLayout getDatasetDiagramLayout(String datasetName); + UUID getCrossProfileDiagramUUID(String datasetName); /** * Creates a new {@link GraphRewindableWithUUIDs} in a specified dataset. If the dataset does diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStoreImpl.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStoreImpl.java index fc9ace93..f2abbad0 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStoreImpl.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStoreImpl.java @@ -31,7 +31,6 @@ import org.apache.jena.sparql.graph.PrefixMappingReadOnly; import org.rdfarchitect.database.DatabaseConnection; import org.rdfarchitect.database.GraphIdentifier; -import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.exception.database.DataAccessException; import org.rdfarchitect.models.cim.queries.select.CIMBaseQueryBuilder; @@ -131,22 +130,22 @@ public Map getDatasetDiagrams(String datasetName) { } @Override - public CrossProfileDiagram getCrossProfileDiagram(String datasetName) { + public DiagramLayout getDatasetDiagramLayout(String datasetName) { lock.lock(); try { assertThatDatasetExists(datasetName); - return graphCollections.get(datasetName).getCrossProfileDiagram(); + return graphCollections.get(datasetName).getDiagramLayout(); } finally { lock.unlock(); } } @Override - public DiagramLayout getDatasetDiagramLayout(String datasetName) { + public UUID getCrossProfileDiagramUUID(String datasetName) { lock.lock(); try { assertThatDatasetExists(datasetName); - return graphCollections.get(datasetName).getDiagramLayout(); + return graphCollections.get(datasetName).getCrossProfileDiagramUUID(); } finally { lock.unlock(); } diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/CrossProfileDiagram.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/GraphSourced.java similarity index 83% rename from backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/CrossProfileDiagram.java rename to backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/GraphSourced.java index f7fa9487..cae52d00 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/CrossProfileDiagram.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/GraphSourced.java @@ -17,14 +17,13 @@ package org.rdfarchitect.database.inmemory.diagrams; +import lombok.AllArgsConstructor; import lombok.Data; import java.util.List; -import java.util.UUID; @Data -public class CrossProfileDiagram { - - private final UUID diagramId; - private List classes; - +@AllArgsConstructor +public class GraphSourced { + private List graphUris; + private T value; } diff --git a/backend/src/main/java/org/rdfarchitect/rdf/graph/wrapper/DiagramLayout.java b/backend/src/main/java/org/rdfarchitect/rdf/graph/wrapper/DiagramLayout.java index 36737408..af64c876 100644 --- a/backend/src/main/java/org/rdfarchitect/rdf/graph/wrapper/DiagramLayout.java +++ b/backend/src/main/java/org/rdfarchitect/rdf/graph/wrapper/DiagramLayout.java @@ -26,19 +26,37 @@ import org.rdfarchitect.dl.rdf.resources.CIM; import java.util.UUID; +import java.util.concurrent.locks.ReentrantReadWriteLock; public class DiagramLayout { @Getter private final Model diagramLayoutModel; - @Getter private final MRID defaultPackageMRID; + private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); + public DiagramLayout() { defaultPackageMRID = new MRID(UUID.randomUUID()); - diagramLayoutModel = ModelFactory.createDefaultModel(); - diagramLayoutModel.setNsPrefix(CIM.PREFIX, CIM.NAMESPACE); diagramLayoutModel.setNsPrefix("rdf", RDF.uri); } + + public T read(java.util.function.Supplier action) { + rwLock.readLock().lock(); + try { + return action.get(); + } finally { + rwLock.readLock().unlock(); + } + } + + public void write(Runnable action) { + rwLock.writeLock().lock(); + try { + action.run(); + } finally { + rwLock.writeLock().unlock(); + } + } } diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java index a5badf04..6257edb6 100644 --- a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java @@ -19,16 +19,29 @@ import lombok.RequiredArgsConstructor; +import org.rdfarchitect.api.dto.crossProfileDiagram.ClassSourceDTO; +import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramDTO; +import org.rdfarchitect.api.dto.crossProfileDiagram.GraphSourcedDTO; +import org.rdfarchitect.api.dto.crossProfileDiagram.MergedClassDTO; import org.rdfarchitect.database.DatabasePort; import org.rdfarchitect.database.GraphIdentifier; import org.rdfarchitect.database.inmemory.diagrams.ClassInDiagram; -import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; +import org.rdfarchitect.dl.data.dto.DiagramObject; +import org.rdfarchitect.dl.data.dto.relations.MRID; +import org.rdfarchitect.dl.queries.select.DLObjectFetcher; +import org.rdfarchitect.services.dl.update.DiagramLayoutServiceUtils; +import org.rdfarchitect.services.select.GetClassListUseCase; import org.springframework.stereotype.Service; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.UUID; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -40,6 +53,7 @@ public class CustomDiagramService RemoveFromDiagramUseCase { private final DatabasePort databasePort; + private final GetClassListUseCase getClassListUseCase; @Override public List getCustomDiagramsForGraph(GraphIdentifier graphIdentifier) { @@ -57,8 +71,74 @@ public List getCustomDiagramsForDataset(String datasetName) { } @Override - public CrossProfileDiagram getCrossProfileDiagram(String datasetName) { - return databasePort.getCrossProfileDiagram(datasetName); + public CrossProfileDiagramDTO getCrossProfileDiagram(String datasetName) { + var graphUris = databasePort.listGraphUris(datasetName); + var crossProfileDiagramUUID = databasePort.getCrossProfileDiagramUUID(datasetName); + var diagramLayout = databasePort.getDatasetDiagramLayout(datasetName); + + diagramLayout.write(() -> { + var model = diagramLayout.getDiagramLayoutModel(); + if (DLObjectFetcher.fetchDiagram(model, crossProfileDiagramUUID) == null) { + DiagramLayoutServiceUtils.insertDiagram( + model, crossProfileDiagramUUID, "CrossProfileDiagram"); + } + }); + + Map mergeMap = new LinkedHashMap<>(); + + for (var graphUri : graphUris) { + var graphIdentifier = new GraphIdentifier(datasetName, graphUri); + var classList = getClassListUseCase.getFullClassList(graphIdentifier); + + for (var dto : classList) { + var classUri = dto.getPrefix() + dto.getLabel(); + var mergedUuid = UUID.nameUUIDFromBytes( + classUri.getBytes(StandardCharsets.UTF_8)); + + var merged = mergeMap.computeIfAbsent(classUri, uri -> + new MergedClassDTO(mergedUuid, uri, + new ArrayList<>(), new ArrayList<>(), + new ArrayList<>(), new ArrayList<>())); + + merged.getSources().add(new ClassSourceDTO(dto.getUuid(), graphUri)); + + if (dto.getAttributes() != null) { + dto.getAttributes().forEach(attr -> + merged.getAttributes().add(new GraphSourcedDTO<>(graphUri, attr))); + } + if (dto.getEnumEntries() != null) { + dto.getEnumEntries().forEach(entry -> + merged.getEnumEntries().add(new GraphSourcedDTO<>(graphUri, entry))); + } + if (dto.getAssociationPairs() != null) { + dto.getAssociationPairs().forEach(assoc -> + merged.getAssociationPairs().add(new GraphSourcedDTO<>(graphUri, assoc))); + } + } + } + + diagramLayout.write(() -> { + var model = diagramLayout.getDiagramLayoutModel(); + var existingDOs = DLObjectFetcher.fetchDiagramDOs( + model, new MRID(crossProfileDiagramUUID)); + var existingClassUUIDs = existingDOs.stream() + .map(DiagramObject::getBelongsToIdentifiedObject) + .map(MRID::getUuid) + .collect(Collectors.toSet()); + + for (var merged : mergeMap.values()) { + if (!existingClassUUIDs.contains(merged.getUuid())) { + var doMRID = DiagramLayoutServiceUtils.insertDiagramObject( + model, + crossProfileDiagramUUID, + merged.getClassUri(), + merged.getUuid()); + DiagramLayoutServiceUtils.insertDiagramObjectPoint(model, doMRID); + } + } + }); + + return new CrossProfileDiagramDTO(crossProfileDiagramUUID, new ArrayList<>(mergeMap.values())); } @Override diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/GetCustomDiagramsUseCase.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/GetCustomDiagramsUseCase.java index 89d1d746..2e299df8 100644 --- a/backend/src/main/java/org/rdfarchitect/services/diagrams/GetCustomDiagramsUseCase.java +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/GetCustomDiagramsUseCase.java @@ -17,12 +17,11 @@ package org.rdfarchitect.services.diagrams; +import java.util.List; +import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramDTO; import org.rdfarchitect.database.GraphIdentifier; -import org.rdfarchitect.database.inmemory.diagrams.CrossProfileDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; -import java.util.List; - public interface GetCustomDiagramsUseCase { /** @@ -47,5 +46,5 @@ public interface GetCustomDiagramsUseCase { * @param datasetName The name of the dataset. * @return The cross profile diagram for the dataset. */ - CrossProfileDiagram getCrossProfileDiagram(String datasetName); + CrossProfileDiagramDTO getCrossProfileDiagram(String datasetName); } diff --git a/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java b/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java index 2875a428..ca25caec 100644 --- a/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java +++ b/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java @@ -42,14 +42,16 @@ public RenderingLayoutData fetchRenderingLayoutData( GraphIdentifier graphIdentifier, UUID packageUUID) { var diagramLayout = databasePort.getGraphWithContext(graphIdentifier).getDiagramLayout(); var diagramLayoutModel = diagramLayout.getDiagramLayoutModel(); - return fetchRenderingLayoutData(diagramLayout, diagramLayoutModel, packageUUID); + return diagramLayout.read(() -> + fetchRenderingLayoutData(diagramLayout, diagramLayoutModel, packageUUID)); } @Override public RenderingLayoutData fetchGlobalRenderingLayoutData(String datasetName, UUID diagramId) { var diagramLayout = databasePort.getDatasetDiagramLayout(datasetName); var diagramLayoutModel = diagramLayout.getDiagramLayoutModel(); - return fetchRenderingLayoutData(diagramLayout, diagramLayoutModel, diagramId); + return diagramLayout.read(() -> + fetchRenderingLayoutData(diagramLayout, diagramLayoutModel, diagramId)); } private RenderingLayoutData fetchRenderingLayoutData( diff --git a/backend/src/main/java/org/rdfarchitect/services/dl/update/UpdateDiagramLayoutService.java b/backend/src/main/java/org/rdfarchitect/services/dl/update/UpdateDiagramLayoutService.java index 56e556df..1b051eb3 100644 --- a/backend/src/main/java/org/rdfarchitect/services/dl/update/UpdateDiagramLayoutService.java +++ b/backend/src/main/java/org/rdfarchitect/services/dl/update/UpdateDiagramLayoutService.java @@ -204,12 +204,16 @@ private String getDiagramName( } private String getDiagramName(String datasetName, UUID diagramUUID) { + var crossProfileUUID = databasePort.getCrossProfileDiagramUUID(datasetName); + if (diagramUUID.equals(crossProfileUUID)) { + return "CrossProfileDiagram"; + } + for (var customDiagram : databasePort.getDatasetDiagrams(datasetName).values()) { if (customDiagram.getDiagramId().equals(diagramUUID)) { return customDiagram.getName(); } } - return null; } } diff --git a/backend/src/main/java/org/rdfarchitect/services/dl/update/classlayout/UpdateClassLayoutService.java b/backend/src/main/java/org/rdfarchitect/services/dl/update/classlayout/UpdateClassLayoutService.java index 0f72b99b..28e205ef 100644 --- a/backend/src/main/java/org/rdfarchitect/services/dl/update/classlayout/UpdateClassLayoutService.java +++ b/backend/src/main/java/org/rdfarchitect/services/dl/update/classlayout/UpdateClassLayoutService.java @@ -84,7 +84,7 @@ public void updateClassPositions( var resolvedPackageUUID = packageUUID != null ? packageUUID : diagramLayout.getDefaultPackageMRID().getUuid(); - updateDiagramObjects(resolvedPackageUUID, classPositionDTOList, diagramLayoutModel); + diagramLayout.write(() -> updateDiagramObjects(resolvedPackageUUID, classPositionDTOList, diagramLayoutModel)); } @Override @@ -93,7 +93,7 @@ public void updateClassPositions( var diagramLayout = databasePort.getDatasetDiagramLayout(datasetName); var diagramLayoutModel = diagramLayout.getDiagramLayoutModel(); - updateDiagramObjects(diagramUUID, classPositionDTOList, diagramLayoutModel); + diagramLayout.write(() -> updateDiagramObjects(diagramUUID, classPositionDTOList, diagramLayoutModel)); } private void updateDiagramObjects( diff --git a/backend/src/main/java/org/rdfarchitect/services/select/GetClassListUseCase.java b/backend/src/main/java/org/rdfarchitect/services/select/GetClassListUseCase.java index ebb6d421..f8e85418 100644 --- a/backend/src/main/java/org/rdfarchitect/services/select/GetClassListUseCase.java +++ b/backend/src/main/java/org/rdfarchitect/services/select/GetClassListUseCase.java @@ -33,4 +33,12 @@ public interface GetClassListUseCase { */ List getClassList( GraphIdentifier graphIdentifier, boolean includeExternalClasses); + + /** + * Gets the list of classes in the graph with full information. + * + * @param graphIdentifier The graph to getClassDefinition. + * @return The list of classes in the graph. + */ + List getFullClassList(GraphIdentifier graphIdentifier); } diff --git a/backend/src/main/java/org/rdfarchitect/services/select/QueryGraphService.java b/backend/src/main/java/org/rdfarchitect/services/select/QueryGraphService.java index 21dcc728..8433128d 100644 --- a/backend/src/main/java/org/rdfarchitect/services/select/QueryGraphService.java +++ b/backend/src/main/java/org/rdfarchitect/services/select/QueryGraphService.java @@ -187,6 +187,32 @@ private List getReferencedClassList(GraphIdentifier graphIde return CIMUMLObjectFactory.createCIMClassUMLAdaptedList(queryResultSet); } + @Override + public List getFullClassList(GraphIdentifier graphIdentifier) { + var prefixMapping = databasePort.getPrefixMapping(graphIdentifier.datasetName()); + var rdfGraph = databasePort.getGraphWithContext(graphIdentifier).getRdfGraph(); + + var baseList = getClassList(graphIdentifier, false); + + rdfGraph.begin(TxnType.READ); + try { + return baseList.stream() + .filter(dto -> dto.getUuid() != null) + .map(dto -> { + var fullClass = CIMUMLObjectFactory.createCIMClassUMLAdapted( + rdfGraph, + graphIdentifier.graphUri(), + prefixMapping, + dto.getUuid().toString()); + fullClass.nullEmptyLists(); + return classMapper.toDTO(fullClass); + }) + .toList(); + } finally { + rdfGraph.end(); + } + } + @Override public List listDatatypes(GraphIdentifier graphIdentifier) { // build query diff --git a/frontend/src/lib/api/backend.js b/frontend/src/lib/api/backend.js index 7a4f0dbd..ba34eadb 100644 --- a/frontend/src/lib/api/backend.js +++ b/frontend/src/lib/api/backend.js @@ -526,6 +526,16 @@ export class BackendConnection { }); } + async getCrossProfileDiagramForDataset(datasetName) { + let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/crossprofilediagram`; + return await fetch(url, { + method: "GET", + mode: "cors", + headers: new Headers({ "Content-Type": "application/json" }), + credentials: "include", + }); + } + async putCustomDiagram(datasetName, graphURI, diagramId, newDiagram) { let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/diagrams/${encodeURIComponent(diagramId)}`; return await fetch(url, { diff --git a/frontend/src/lib/rendering/svelteflow/CrossProfileDiagram.svelte b/frontend/src/lib/rendering/svelteflow/CrossProfileDiagram.svelte new file mode 100644 index 00000000..23eb3f65 --- /dev/null +++ b/frontend/src/lib/rendering/svelteflow/CrossProfileDiagram.svelte @@ -0,0 +1,159 @@ + + + + +
+ + + +
+ {#if !error} + + {/if} +
+ + {#if isLoading || isFlowLoading} +
+ +
+ {:else if error} +
+

{error}

+
+ {/if} +
+ + + {#if selectedMergedClass} + {#key classEditorKey} + + {/key} + {/if} + +
+
+
diff --git a/frontend/src/lib/rendering/svelteflow/components/crossProfileDiagramUtils.js b/frontend/src/lib/rendering/svelteflow/components/crossProfileDiagramUtils.js new file mode 100644 index 00000000..3782f0ac --- /dev/null +++ b/frontend/src/lib/rendering/svelteflow/components/crossProfileDiagramUtils.js @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +export function mapCrossProfileDiagramToFlow(dto) { + const uuidByClassUri = new Map(dto.classes.map(c => [c.classUri, c.uuid])); + + const nodes = dto.classes.map(mergedClass => { + console.log("attributes raw:", mergedClass.attributes); + console.log("enumEntries raw:", mergedClass.enumEntries); + return { + id: mergedClass.uuid, + type: "class", + position: { x: 0, y: 0 }, + data: { + label: extractLabel(mergedClass.classUri), + stereotypes: [], + attributes: mergedClass.attributes.map(graphSourced => ({ + label: graphSourced.value.label ?? "", + type: graphSourced.value.dataType + ? (graphSourced.value.dataType.label ?? "") + : "", + multiplicity: graphSourced.value.multiplicity ?? "", + })), + enumEntries: mergedClass.enumEntries.map( + graphSourced => graphSourced.value.label ?? "", + ), + belongsToCategory: null, + graphUri: mergedClass.sources[0]?.graphUri ?? null, + }, + }; + }); + + const edges = buildEdges(dto.classes, uuidByClassUri); + + return { nodes, edges }; +} + +function extractLabel(classUri) { + const hash = classUri.lastIndexOf("#"); + const slash = classUri.lastIndexOf("/"); + const idx = Math.max(hash, slash); + return idx >= 0 ? classUri.substring(idx + 1) : classUri; +} + +function buildEdges(classes, uuidByClassUri) { + const edges = []; + const edgeIds = new Set(); + + for (const mergedClass of classes) { + for (const graphSourced of mergedClass.associationPairs) { + const pair = graphSourced.value; + const targetPrefix = pair.to?.range?.prefix ?? ""; + const targetLabel = pair.to?.range?.label ?? ""; + const targetClassUri = targetPrefix + targetLabel; + const targetUuid = uuidByClassUri.get(targetClassUri); + + if (!targetUuid) continue; + + const edgeId = `${mergedClass.uuid}→${pair.from?.uuid ?? pair.to?.uuid}→${targetUuid}`; + if (edgeIds.has(edgeId)) continue; + edgeIds.add(edgeId); + + edges.push({ + id: edgeId, + source: mergedClass.uuid, + target: targetUuid, + type: "association", + data: { + label: pair.from?.label ?? "", + multiplicity: pair.to?.multiplicity ?? "", + }, + }); + } + } + + return edges; +} diff --git a/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte b/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte index a0953a8d..5cce7dea 100644 --- a/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte +++ b/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte @@ -470,6 +470,7 @@ } const diagramUUID = editorState.selectedDiagram.getProperty("id"); + if (!diagramUUID) return; if (editorState.selectedGraph.getValue()) { bec.updateClassPositions( diff --git a/frontend/src/lib/sharedState.svelte.js b/frontend/src/lib/sharedState.svelte.js index 57c6edd4..4a4bdc88 100644 --- a/frontend/src/lib/sharedState.svelte.js +++ b/frontend/src/lib/sharedState.svelte.js @@ -37,6 +37,7 @@ import { export const DiagramType = { CUSTOM_GRAPH_DIAGRAM: "customGraphDiagram", CUSTOM_DATASET_DIAGRAM: "customDatasetDiagram", + CROSS_PROFILE: "crossProfile", PACKAGE: "package", }; diff --git a/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte b/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte new file mode 100644 index 00000000..b7f75b6b --- /dev/null +++ b/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte @@ -0,0 +1,71 @@ + + + + +{#if mergedClass && mergedClass.sources?.length > 0} +
+ {#each mergedClass.sources as source, i} + + {/each} +
+ + {#if activeSource} + {#key activeSource.classUuid + activeSource.graphUri} +
+ +
+ {/key} + {/if} +{:else} +

+ No sources available for this class. +

+{/if} diff --git a/frontend/src/routes/mainpage/packageNavigation/DatasetSection.svelte b/frontend/src/routes/mainpage/packageNavigation/DatasetSection.svelte index 188b5b9b..eb618209 100644 --- a/frontend/src/routes/mainpage/packageNavigation/DatasetSection.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/DatasetSection.svelte @@ -26,6 +26,7 @@ faLock, faDiagramProject, faPlus, + faShareNodes, } from "@fortawesome/free-solid-svg-icons"; import { getContext } from "svelte"; @@ -34,8 +35,11 @@ import { ContextMenu } from "$lib/components/bitsui/contextmenu"; import NavigationEntry from "$lib/components/navigation/NavigationEntry.svelte"; import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; - import { forceReloadTrigger } from "$lib/sharedState.svelte.js"; - import { editorState } from "$lib/sharedState.svelte.js"; + import { + DiagramType, + editorState, + forceReloadTrigger, + } from "$lib/sharedState.svelte.js"; import CustomDiagramsSection from "./CustomDiagramsSection.svelte"; import GraphSection from "./GraphSection.svelte"; @@ -232,6 +236,34 @@ /> {/each} + + { + editorState.selectedDataset.updateValue( + datasetNavEntry.label, + ); + editorState.selectedGraph.updateValue(null); + editorState.selectedClassUUID.updateValue(null); + editorState.selectedDiagram.updateValue({ + type: DiagramType.CROSS_PROFILE, + id: null, + }); + }} + /> + import { Pane, Splitpanes } from "svelte-splitpanes"; - import { editorState } from "$lib/sharedState.svelte.js"; + import CrossProfileDiagram from "$lib/rendering/svelteflow/CrossProfileDiagram.svelte"; + import { DiagramType, editorState } from "$lib/sharedState.svelte.js"; import ClassEditor from "./classEditor/classEditor.svelte"; import RenderingWrapper from "./renderingWrapper.svelte"; let classEditorPaneWidth = $state(30); let paneSizeByPackage = $state({}); + let classDatasetName = $derived( editorState.selectedClassDataset.getValue() ?? editorState.selectedDataset.getValue(), @@ -48,6 +50,12 @@ `${editorState.selectedDataset.getValue() ?? ""}::${editorState.selectedGraph.getValue() ?? ""}::${editorState.selectedDiagram.getProperty("id") ?? ""}`, ); + const diagramType = $derived( + editorState.selectedDiagram.getProperty("type"), + ); + const isCrossProfile = $derived(diagramType === DiagramType.CROSS_PROFILE); + const selectedDataset = $derived(editorState.selectedDataset.getValue()); + $effect(() => { editorState.selectedDataset.subscribe(); editorState.selectedGraph.subscribe(); @@ -75,7 +83,6 @@ if (!editorState.selectedClassUUID.getValue()) { return; } - // event.detail[1] holds the size of the class editor pane. classEditorPaneWidth = event.detail[1].size; const packageKey = getPackageKey(); if (packageKey) { @@ -89,36 +96,42 @@
- - + {/key} + {:else} + - {#key renderingKey} -
- -
- {/key} -
+ + {#key renderingKey} +
+ +
+ {/key} +
- {#if isClassSelected} - {#key classEditorKey} - - {/key} + {#if isClassSelected} + {#key classEditorKey} + + {/key} + {/if} - {/if} -
+ + {/if}
From aeed5ad567cac75a5db20d82177e86e0e517ee64 Mon Sep 17 00:00:00 2001 From: Philipp Date: Mon, 1 Jun 2026 15:20:52 +0200 Subject: [PATCH 03/30] temp --- ...rossProfileDiagramColorRestController.java | 95 ++++++++ .../CrossProfileDiagramIDController.java | 65 ++++++ ...ProfileDiagramRenderingRestController.java | 86 ++++++++ .../CrossProfileDiagramRestController.java | 19 +- .../crossProfileDiagram/ClassSourceDTO.java | 1 + .../CrossProfileDiagramColorDataDTO.java | 29 +++ .../CrossProfileDiagramDTO.java | 1 + .../crossProfileDiagram/GraphSourcedDTO.java | 1 + .../crossProfileDiagram/MergedClassDTO.java | 17 +- .../svelteflow/sub/AttributeDTO.java | 2 + .../rendering/svelteflow/sub/EdgeDataDTO.java | 1 + .../svelteflow/sub/EnumEntryDTO.java | 31 +++ .../rendering/svelteflow/sub/NodeDataDTO.java | 2 +- .../database/inmemory/GraphWithContext.java | 16 ++ .../inmemory/GraphWithContextCollection.java | 3 +- .../inmemory/diagrams/GraphSourced.java | 1 + .../models/cim/data/dto/CIMAssociation.java | 2 + .../models/cim/data/dto/CIMAttribute.java | 4 + .../models/cim/data/dto/CIMCollection.java | 15 +- .../models/cim/data/dto/CIMEnumEntry.java | 4 + .../models/cim/data/dto/CIMMergedClass.java | 36 ++++ .../RenderCIMCollectionSvelteFlowService.java | 40 +++- .../diagrams/CrossProfileColorUseCase.java | 38 ++++ .../diagrams/CustomDiagramService.java | 182 +++++++++++----- .../diagrams/GetCustomDiagramsUseCase.java | 6 +- .../dl/select/QueryDiagramLayoutService.java | 8 +- .../classlayout/UpdateClassLayoutService.java | 8 +- ...iagramToCIMCollectionConverterService.java | 93 ++++++++ ...iagramToCIMCollectionConverterUseCase.java | 9 + .../GraphToCIMCollectionConverterService.java | 40 +++- .../services/select/QueryGraphService.java | 20 +- .../diagrams/CustomDiagramsServiceTest.java | 4 +- frontend/src/lib/api/apiDatasetUtils.js | 6 + frontend/src/lib/api/backend.js | 41 ++++ .../svelteflow/CrossProfileDiagram.svelte | 159 -------------- .../svelteflow/components/ClassNode.svelte | 97 ++++++++- .../SvelteFlowClassContextMenu.svelte | 202 +++++++++--------- .../SvelteFlowPaneContextMenu.svelte | 23 +- .../components/crossProfileDiagramUtils.js | 91 -------- .../svelteflow/svelteFlowWrapper.svelte | 14 ++ frontend/src/lib/sharedState.svelte.js | 7 + frontend/src/routes/GraphDeleteDialog.svelte | 1 + frontend/src/routes/ImportDialog.svelte | 1 + frontend/src/routes/NewClassDialog.svelte | 4 + frontend/src/routes/Searchbar.svelte | 2 + .../DeleteDependenciesDialog.svelte | 1 + .../mainpage/classEditor/classEditor.svelte | 12 +- .../components/ClassEditorButtons.svelte | 3 + .../classEditor/mergedClassEditor.svelte | 68 ++++-- .../packageNavigation/ClassEntry.svelte | 3 + .../CrossProfileDiagramsSection.svelte | 78 +++++++ .../packageNavigation/DatasetSection.svelte | 43 ++-- .../CrossProfileColorDialog.svelte | 179 ++++++++++++++++ .../src/routes/mainpage/packageWindow.svelte | 73 ++++--- .../routes/mainpage/renderingWrapper.svelte | 27 +++ 55 files changed, 1457 insertions(+), 557 deletions(-) create mode 100644 backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramColorRestController.java create mode 100644 backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramIDController.java create mode 100644 backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRenderingRestController.java create mode 100644 backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/CrossProfileDiagramColorDataDTO.java create mode 100644 backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/EnumEntryDTO.java create mode 100644 backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java create mode 100644 backend/src/main/java/org/rdfarchitect/services/diagrams/CrossProfileColorUseCase.java delete mode 100644 frontend/src/lib/rendering/svelteflow/CrossProfileDiagram.svelte delete mode 100644 frontend/src/lib/rendering/svelteflow/components/crossProfileDiagramUtils.js create mode 100644 frontend/src/routes/mainpage/packageNavigation/CrossProfileDiagramsSection.svelte create mode 100644 frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CrossProfileColorDialog.svelte diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramColorRestController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramColorRestController.java new file mode 100644 index 00000000..a1f4f413 --- /dev/null +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramColorRestController.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.api.controller.datasets.diagrams; + +import io.swagger.v3.oas.annotations.Parameter; + +import lombok.RequiredArgsConstructor; + +import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramColorDataDTO; +import org.rdfarchitect.services.diagrams.CrossProfileColorUseCase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/datasets/{datasetName}/crossprofilediagramColors") +@RequiredArgsConstructor +public class CrossProfileDiagramColorRestController { + + private static final Logger logger = + LoggerFactory.getLogger(CrossProfileDiagramColorRestController.class); + + private final CrossProfileColorUseCase colorUseCase; + + @GetMapping + public CrossProfileDiagramColorDataDTO getCrossProfileColors( + @Parameter(description = "The name/url of the inquirer.") + @RequestHeader( + value = HttpHeaders.ORIGIN, + required = false, + defaultValue = "unknown") + String originURL, + @Parameter(description = "The literal name of the dataset.") @PathVariable + String datasetName) { + logger.info( + "Received GET request: \"/api/datasets/{{}}/crossprofilediagramColors\" from \"{}\"", + datasetName, + originURL); + + var result = colorUseCase.getCrossProfileColors(datasetName); + + logger.info( + "Sending response to GET request: \"/api/datasets/{{}}/crossprofilediagramColors\" from \"{}\"", + datasetName, + originURL); + return result; + } + + @PutMapping + public void updateCrossProfileColors( + @Parameter(description = "The name/url of the inquirer.") + @RequestHeader( + value = HttpHeaders.ORIGIN, + required = false, + defaultValue = "unknown") + String originURL, + @Parameter(description = "The literal name of the dataset.") @PathVariable + String datasetName, + @RequestBody CrossProfileDiagramColorDataDTO colorData) { + + logger.info( + "Received PUT request: \"/api/datasets/{{}}/crossprofilediagramColors\" from \"{}\"", + datasetName, + originURL); + + colorUseCase.replaceCrossProfileColors(datasetName, colorData); + + logger.info( + "Sending response to PUT request: \"/api/datasets/{{}}/crossprofilediagramColors\" from \"{}\"", + datasetName, + originURL); + } +} diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramIDController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramIDController.java new file mode 100644 index 00000000..ed00cf15 --- /dev/null +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramIDController.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.api.controller.datasets.diagrams; + +import io.swagger.v3.oas.annotations.Parameter; + +import lombok.RequiredArgsConstructor; + +import org.rdfarchitect.database.DatabasePort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/datasets/{datasetName}/crossprofilediagramID") +@RequiredArgsConstructor +public class CrossProfileDiagramIDController { + + private static final Logger logger = + LoggerFactory.getLogger(CrossProfileDiagramIDController.class); + + private final DatabasePort databasePort; + + @GetMapping + public String getCrossProfileRenderingData( + @Parameter(description = "The name/url of the inquirer.") + @RequestHeader( + value = HttpHeaders.ORIGIN, + required = false, + defaultValue = "unknown") + String originURL, + @Parameter(description = "The literal name of the dataset.") @PathVariable + String datasetName) { + logger.info( + "Received GET request: \"/api/datasets/{{}}/crossprofilediagram\" from \"{}\"", + datasetName, + originURL); + + logger.info( + "Sending response to GET request: \"/api/datasets/{{}}/crossprofilediagram\" from \"{}\"", + datasetName, + originURL); + return databasePort.getCrossProfileDiagramUUID(datasetName).toString(); + } +} diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRenderingRestController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRenderingRestController.java new file mode 100644 index 00000000..da92c706 --- /dev/null +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRenderingRestController.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.api.controller.datasets.diagrams; + +import io.swagger.v3.oas.annotations.Parameter; + +import lombok.RequiredArgsConstructor; + +import org.rdfarchitect.api.dto.rendering.RenderingDataDTO; +import org.rdfarchitect.database.DatabasePort; +import org.rdfarchitect.models.cim.rendering.RenderCIMCollectionUseCase; +import org.rdfarchitect.services.diagrams.GetCustomDiagramsUseCase; +import org.rdfarchitect.services.rendering.DiagramToCIMCollectionConverterUseCase; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/datasets/{datasetName}/crossprofilediagramRendering") +@RequiredArgsConstructor +public class CrossProfileDiagramRenderingRestController { + + private static final Logger logger = + LoggerFactory.getLogger(CrossProfileDiagramRenderingRestController.class); + + private final DiagramToCIMCollectionConverterUseCase converter; + + private final RenderCIMCollectionUseCase renderer; + + private final GetCustomDiagramsUseCase getCustomDiagramsUseCase; + + private final DatabasePort databasePort; + + @GetMapping + public RenderingDataDTO getCrossProfileRenderingData( + @Parameter(description = "The name/url of the inquirer.") + @RequestHeader( + value = HttpHeaders.ORIGIN, + required = false, + defaultValue = "unknown") + String originURL, + @Parameter(description = "The literal name of the dataset.") @PathVariable + String datasetName) { + logger.info( + "Received GET request: \"/api/datasets/{{}}/crossprofilediagramRendering\" from \"{}\"", + datasetName, + originURL); + + var crossProfileDiagram = + getCustomDiagramsUseCase.getCrossProfileDiagram(datasetName, true, true); + + var cimCollection = converter.convert(crossProfileDiagram); + + var result = + renderer.renderGlobalUML( + cimCollection, + datasetName, + databasePort.getCrossProfileDiagramUUID(datasetName)); + + logger.info( + "Sending response to GET request: \"/api/datasets/{{}}/crossprofilediagramRendering\" from \"{}\"", + datasetName, + originURL); + return result; + } +} diff --git a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestController.java b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestController.java index 58f3f390..6ba71057 100644 --- a/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestController.java +++ b/backend/src/main/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestController.java @@ -18,7 +18,9 @@ package org.rdfarchitect.api.controller.datasets.diagrams; import io.swagger.v3.oas.annotations.Parameter; + import lombok.RequiredArgsConstructor; + import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramDTO; import org.rdfarchitect.services.diagrams.GetCustomDiagramsUseCase; import org.slf4j.Logger; @@ -41,21 +43,21 @@ public class CrossProfileDiagramRestController { private final GetCustomDiagramsUseCase getCustomDiagramsUseCase; @GetMapping - public CrossProfileDiagramDTO getCustomDiagramList( + public CrossProfileDiagramDTO getCrossProfileRenderingData( @Parameter(description = "The name/url of the inquirer.") - @RequestHeader( - value = HttpHeaders.ORIGIN, - required = false, - defaultValue = "unknown") - String originURL, + @RequestHeader( + value = HttpHeaders.ORIGIN, + required = false, + defaultValue = "unknown") + String originURL, @Parameter(description = "The literal name of the dataset.") @PathVariable - String datasetName) { + String datasetName) { logger.info( "Received GET request: \"/api/datasets/{{}}/crossprofilediagram\" from \"{}\"", datasetName, originURL); - var result = getCustomDiagramsUseCase.getCrossProfileDiagram(datasetName); + var result = getCustomDiagramsUseCase.getCrossProfileDiagram(datasetName, false, false); logger.info( "Sending response to GET request: \"/api/datasets/{{}}/crossprofilediagram\" from \"{}\"", @@ -63,5 +65,4 @@ public CrossProfileDiagramDTO getCustomDiagramList( originURL); return result; } - } diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java index 757f925b..d86d5622 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java @@ -19,6 +19,7 @@ import lombok.AllArgsConstructor; import lombok.Data; + import java.util.UUID; @Data diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/CrossProfileDiagramColorDataDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/CrossProfileDiagramColorDataDTO.java new file mode 100644 index 00000000..b21130b3 --- /dev/null +++ b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/CrossProfileDiagramColorDataDTO.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.api.dto.crossProfileDiagram; + +import lombok.AllArgsConstructor; +import lombok.Data; + +import java.util.Map; + +@Data +@AllArgsConstructor +public class CrossProfileDiagramColorDataDTO { + Map graphColors; +} diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/CrossProfileDiagramDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/CrossProfileDiagramDTO.java index 412e16bd..10c72fcd 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/CrossProfileDiagramDTO.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/CrossProfileDiagramDTO.java @@ -19,6 +19,7 @@ import lombok.AllArgsConstructor; import lombok.Data; + import java.util.List; import java.util.UUID; diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/GraphSourcedDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/GraphSourcedDTO.java index beec6f25..0daf1317 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/GraphSourcedDTO.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/GraphSourcedDTO.java @@ -24,5 +24,6 @@ @AllArgsConstructor public class GraphSourcedDTO { private String graphUri; + private String graphColor; private T value; } diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java index 352d0808..a9c34507 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java @@ -17,13 +17,17 @@ package org.rdfarchitect.api.dto.crossProfileDiagram; -import java.util.List; -import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Data; + +import org.rdfarchitect.api.dto.SuperClassDTO; import org.rdfarchitect.api.dto.association.AssociationPairDTO; import org.rdfarchitect.api.dto.attributes.AttributeDTO; import org.rdfarchitect.api.dto.enumentries.EnumEntryDTO; +import org.rdfarchitect.models.cim.data.dto.relations.CIMSStereotype; + +import java.util.List; +import java.util.UUID; @Data @AllArgsConstructor @@ -31,9 +35,10 @@ public class MergedClassDTO { private UUID uuid; private String classUri; - private List sources; - private List> attributes; - private List> enumEntries; + private List sources; + private List> superClasses; + private List> attributes; + private List> enumEntries; private List> associationPairs; - + private List stereotypes; } diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/AttributeDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/AttributeDTO.java index a1366440..c5b9be64 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/AttributeDTO.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/AttributeDTO.java @@ -28,4 +28,6 @@ public class AttributeDTO { private String label; private String type; private String multiplicity; + private String graphUri; + private String color; } diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/EdgeDataDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/EdgeDataDTO.java index 54a6f669..43ed1950 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/EdgeDataDTO.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/EdgeDataDTO.java @@ -29,4 +29,5 @@ public class EdgeDataDTO { private String fromMultiplicity; private boolean useToAssociation; private boolean useFromAssociation; + private String graphUri; } diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/EnumEntryDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/EnumEntryDTO.java new file mode 100644 index 00000000..8a41617e --- /dev/null +++ b/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/EnumEntryDTO.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.api.dto.rendering.svelteflow.sub; + +import lombok.Builder; +import lombok.Data; + +/** DTO representing an enum entry used in the SvelteFlow node data DTO. */ +@Data +@Builder +public class EnumEntryDTO { + + private String label; + private String graphUri; + private String color; +} diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/NodeDataDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/NodeDataDTO.java index 664ca16d..349e89ea 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/NodeDataDTO.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/NodeDataDTO.java @@ -32,5 +32,5 @@ public class NodeDataDTO { private String belongsToCategory; private List stereotypes; private List attributes; - private List enumEntries; + private List enumEntries; } diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java index 7b1fcded..41e19e8e 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java @@ -25,6 +25,8 @@ import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; import org.rdfarchitect.rdf.graph.wrapper.GraphRewindableWithUUIDs; +import java.awt.Color; +import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -43,7 +45,21 @@ public class GraphWithContext { @Getter @Setter private Model customSHACL; + @Getter @Setter private String crossProfileDiagramColor; + public GraphWithContext(GraphRewindableWithUUIDs rdfGraph) { this.rdfGraph = rdfGraph; + this.crossProfileDiagramColor = generateRandomDarkColor(); + } + + private static String generateRandomDarkColor() { + Random random = new Random(); + + float hue = random.nextFloat(); + float saturation = 0.5f + random.nextFloat() * 0.5f; + float brightness = 0.3f + random.nextFloat() * 0.4f; + + Color color = Color.getHSBColor(hue, saturation, brightness); + return String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue()); } } diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java index 53a74bc0..4f7b997a 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java @@ -66,8 +66,7 @@ public class GraphWithContextCollection { @Getter private final DiagramLayout diagramLayout = new DiagramLayout(); - @Getter - private final UUID crossProfileDiagramUUID = UUID.randomUUID(); + @Getter private final UUID crossProfileDiagramUUID = UUID.randomUUID(); // lock to prohibit dirty reads/writes private final ReentrantLock lock = new ReentrantLock(); diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/GraphSourced.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/GraphSourced.java index cae52d00..b44da138 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/GraphSourced.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/diagrams/GraphSourced.java @@ -19,6 +19,7 @@ import lombok.AllArgsConstructor; import lombok.Data; + import java.util.List; @Data diff --git a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMAssociation.java b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMAssociation.java index c7382027..e71024a4 100644 --- a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMAssociation.java +++ b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMAssociation.java @@ -39,6 +39,8 @@ public class CIMAssociation { private URI uri; + private String graphUri; + private RDFSLabel label; private RDFSComment comment; diff --git a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMAttribute.java b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMAttribute.java index 673f322a..75709687 100644 --- a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMAttribute.java +++ b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMAttribute.java @@ -40,6 +40,10 @@ public class CIMAttribute { private URI uri; + private String graphUri; + + private String color; + private RDFSLabel label; private RDFSDomain domain; diff --git a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMCollection.java b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMCollection.java index c4ed2f29..4c5776d3 100644 --- a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMCollection.java +++ b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMCollection.java @@ -31,13 +31,22 @@ public CIMCollection() { classes = new TreeSet<>(Comparator.comparing(cimClass -> cimClass.getUri().toString())); attributes = new TreeSet<>( - Comparator.comparing(cimAttribute -> cimAttribute.getUri().toString())); + Comparator.comparing( + (CIMAttribute a) -> + a.getGraphUri() != null ? a.getGraphUri() : "") + .thenComparing(a -> a.getUri().toString())); associations = new TreeSet<>( - Comparator.comparing(cimAssociation -> cimAssociation.getUri().toString())); + Comparator.comparing((CIMAssociation a) -> a.getUri().toString()) + .thenComparing( + a -> a.getGraphUri() != null ? a.getGraphUri() : "")); enums = new TreeSet<>(Comparator.comparing(cimEnum -> cimEnum.getUri().toString())); enumEntries = - new TreeSet<>(Comparator.comparing(enumEntry -> enumEntry.getUri().toString())); + new TreeSet<>( + Comparator.comparing( + (CIMEnumEntry a) -> + a.getGraphUri() != null ? a.getGraphUri() : "") + .thenComparing(a -> a.getUri().toString())); } private final SortedSet packages; diff --git a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMEnumEntry.java b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMEnumEntry.java index ec6d7e75..53d95d00 100644 --- a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMEnumEntry.java +++ b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMEnumEntry.java @@ -36,6 +36,10 @@ public class CIMEnumEntry { private URI uri; + private String graphUri; + + private String color; + private RDFType type; private RDFSLabel label; diff --git a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java new file mode 100644 index 00000000..23b62a48 --- /dev/null +++ b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.models.cim.data.dto; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import org.rdfarchitect.models.cim.data.dto.relations.RDFSSubClassOf; + +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Data +@SuperBuilder(toBuilder = true) +@NoArgsConstructor +public class CIMMergedClass extends CIMClass { + + private List superClasses; +} diff --git a/backend/src/main/java/org/rdfarchitect/models/cim/rendering/svelteflow/RenderCIMCollectionSvelteFlowService.java b/backend/src/main/java/org/rdfarchitect/models/cim/rendering/svelteflow/RenderCIMCollectionSvelteFlowService.java index c0657a20..ff934da8 100644 --- a/backend/src/main/java/org/rdfarchitect/models/cim/rendering/svelteflow/RenderCIMCollectionSvelteFlowService.java +++ b/backend/src/main/java/org/rdfarchitect/models/cim/rendering/svelteflow/RenderCIMCollectionSvelteFlowService.java @@ -25,6 +25,7 @@ import org.rdfarchitect.api.dto.rendering.svelteflow.sub.AttributeDTO; import org.rdfarchitect.api.dto.rendering.svelteflow.sub.EdgeDTO; import org.rdfarchitect.api.dto.rendering.svelteflow.sub.EdgeDataDTO; +import org.rdfarchitect.api.dto.rendering.svelteflow.sub.EnumEntryDTO; import org.rdfarchitect.api.dto.rendering.svelteflow.sub.NodeDTO; import org.rdfarchitect.api.dto.rendering.svelteflow.sub.NodeDataDTO; import org.rdfarchitect.api.dto.rendering.svelteflow.sub.PositionDTO; @@ -32,6 +33,7 @@ import org.rdfarchitect.models.cim.data.dto.CIMAssociation; import org.rdfarchitect.models.cim.data.dto.CIMClass; import org.rdfarchitect.models.cim.data.dto.CIMCollection; +import org.rdfarchitect.models.cim.data.dto.CIMMergedClass; import org.rdfarchitect.models.cim.data.dto.relations.CIMSAssociationUsed; import org.rdfarchitect.models.cim.data.dto.relations.CIMSMultiplicity; import org.rdfarchitect.models.cim.data.dto.relations.CIMSStereotype; @@ -177,6 +179,8 @@ private List getClassAttributes(RenderContext renderContext, CIMCl .label(cimAttribute.getLabel().getValue()) .type(cimAttribute.getDataType().getLabel().getValue()) .multiplicity(extractMultiplicityString(cimAttribute.getMultiplicity())) + .graphUri(cimAttribute.getGraphUri()) + .color(cimAttribute.getColor()) .build()); } @@ -219,14 +223,19 @@ private List getClassStereotypes(CIMClass cimClass) { * @param cimClass The CIM class * @return List of enum entry labels */ - private List getClassEnumEntries(RenderContext renderContext, CIMClass cimClass) { - List enumEntries = new ArrayList<>(); + private List getClassEnumEntries(RenderContext renderContext, CIMClass cimClass) { + List enumEntries = new ArrayList<>(); for (var cimEnumEntry : renderContext.cimCollection.getEnumEntries()) { if (!cimEnumEntry.getType().getUri().equals(cimClass.getUri())) { continue; } - enumEntries.add(cimEnumEntry.getLabel().getValue()); + enumEntries.add( + EnumEntryDTO.builder() + .label(cimEnumEntry.getLabel().getValue()) + .graphUri(cimEnumEntry.getGraphUri()) + .color(cimEnumEntry.getColor()) + .build()); } return enumEntries; @@ -258,7 +267,18 @@ private List assembleInheritanceEdgeDTOList(RenderContext renderContext List inheritanceEdgeDTOList = new ArrayList<>(); for (var cimClass : renderContext.cimCollection.getClasses()) { if (cimClass.getSuperClass() != null) { - inheritanceEdgeDTOList.add(assembleInheritanceEdgeDTO(renderContext, cimClass)); + inheritanceEdgeDTOList.add( + assembleInheritanceEdgeDTO( + renderContext, + cimClass.getUri(), + cimClass.getSuperClass().getUri())); + } + if (cimClass instanceof CIMMergedClass cimMergedClass) { + for (var superClass : cimMergedClass.getSuperClasses()) { + inheritanceEdgeDTOList.add( + assembleInheritanceEdgeDTO( + renderContext, cimClass.getUri(), superClass.getUri())); + } } } return inheritanceEdgeDTOList; @@ -267,13 +287,14 @@ private List assembleInheritanceEdgeDTOList(RenderContext renderContext /** * Assembles an inheritance edge from a class to its superclass. * - * @param cimClass The child class + * @param classURI The URI of the child class + * @param superClassURI The URI of the parent class * @return EdgeDTO representing the inheritance relationship */ - private EdgeDTO assembleInheritanceEdgeDTO(RenderContext renderContext, CIMClass cimClass) { - var classUUID = renderContext.uriToUUIDMap.get(cimClass.getUri().toString()); - var superClassUUID = - renderContext.uriToUUIDMap.get(cimClass.getSuperClass().getUri().toString()); + private EdgeDTO assembleInheritanceEdgeDTO( + RenderContext renderContext, URI classURI, URI superClassURI) { + var classUUID = renderContext.uriToUUIDMap.get(classURI.toString()); + var superClassUUID = renderContext.uriToUUIDMap.get(superClassURI.toString()); return EdgeDTO.builder() .id(UUID.randomUUID().toString()) @@ -340,6 +361,7 @@ private EdgeDTO assembleAssociationEdgeDTO( .fromMultiplicity(fromMultiplicity) .useToAssociation(useToAssociation) .useFromAssociation(useFromAssociation) + .graphUri(from.getGraphUri()) .build(); return EdgeDTO.builder() diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/CrossProfileColorUseCase.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/CrossProfileColorUseCase.java new file mode 100644 index 00000000..d22a9ba2 --- /dev/null +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/CrossProfileColorUseCase.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.services.diagrams; + +import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramColorDataDTO; + +public interface CrossProfileColorUseCase { + + /** + * Returns the colors of the graphs used in the cross profile diagram. + * + * @param datasetName The dataset of the diagram. + * @return The colors of the graphs. + */ + CrossProfileDiagramColorDataDTO getCrossProfileColors(String datasetName); + + /** + * Replaces the colors of the graphs used in the cross profile diagram. + * + * @param datasetName The dataset of the diagram. + */ + void replaceCrossProfileColors(String datasetName, CrossProfileDiagramColorDataDTO dto); +} diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java index 6257edb6..bee506a9 100644 --- a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java @@ -19,7 +19,9 @@ import lombok.RequiredArgsConstructor; +import org.rdfarchitect.api.dto.ClassUMLAdaptedDTO; import org.rdfarchitect.api.dto.crossProfileDiagram.ClassSourceDTO; +import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramColorDataDTO; import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramDTO; import org.rdfarchitect.api.dto.crossProfileDiagram.GraphSourcedDTO; import org.rdfarchitect.api.dto.crossProfileDiagram.MergedClassDTO; @@ -30,12 +32,15 @@ import org.rdfarchitect.dl.data.dto.DiagramObject; import org.rdfarchitect.dl.data.dto.relations.MRID; import org.rdfarchitect.dl.queries.select.DLObjectFetcher; +import org.rdfarchitect.models.cim.data.dto.relations.CIMSStereotype; +import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; import org.rdfarchitect.services.dl.update.DiagramLayoutServiceUtils; import org.rdfarchitect.services.select.GetClassListUseCase; import org.springframework.stereotype.Service; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -50,7 +55,8 @@ public class CustomDiagramService ReplaceCustomDiagramUseCase, DeleteCustomDiagramUseCase, AddToDiagramUseCase, - RemoveFromDiagramUseCase { + RemoveFromDiagramUseCase, + CrossProfileColorUseCase { private final DatabasePort databasePort; private final GetClassListUseCase getClassListUseCase; @@ -71,74 +77,129 @@ public List getCustomDiagramsForDataset(String datasetName) { } @Override - public CrossProfileDiagramDTO getCrossProfileDiagram(String datasetName) { + public CrossProfileDiagramDTO getCrossProfileDiagram( + String datasetName, boolean includeProperties, boolean doLayout) { var graphUris = databasePort.listGraphUris(datasetName); var crossProfileDiagramUUID = databasePort.getCrossProfileDiagramUUID(datasetName); var diagramLayout = databasePort.getDatasetDiagramLayout(datasetName); - diagramLayout.write(() -> { - var model = diagramLayout.getDiagramLayoutModel(); - if (DLObjectFetcher.fetchDiagram(model, crossProfileDiagramUUID) == null) { - DiagramLayoutServiceUtils.insertDiagram( - model, crossProfileDiagramUUID, "CrossProfileDiagram"); - } - }); - Map mergeMap = new LinkedHashMap<>(); for (var graphUri : graphUris) { var graphIdentifier = new GraphIdentifier(datasetName, graphUri); var classList = getClassListUseCase.getFullClassList(graphIdentifier); + var graphColor = + databasePort.getGraphWithContext(graphIdentifier).getCrossProfileDiagramColor(); for (var dto : classList) { var classUri = dto.getPrefix() + dto.getLabel(); - var mergedUuid = UUID.nameUUIDFromBytes( - classUri.getBytes(StandardCharsets.UTF_8)); + var mergedUuid = UUID.nameUUIDFromBytes(classUri.getBytes(StandardCharsets.UTF_8)); - var merged = mergeMap.computeIfAbsent(classUri, uri -> - new MergedClassDTO(mergedUuid, uri, - new ArrayList<>(), new ArrayList<>(), - new ArrayList<>(), new ArrayList<>())); + var merged = + mergeMap.computeIfAbsent( + classUri, + uri -> + new MergedClassDTO( + mergedUuid, + uri, + new ArrayList<>(), + new ArrayList<>(), + new ArrayList<>(), + new ArrayList<>(), + new ArrayList<>(), + new ArrayList<>())); merged.getSources().add(new ClassSourceDTO(dto.getUuid(), graphUri)); - if (dto.getAttributes() != null) { - dto.getAttributes().forEach(attr -> - merged.getAttributes().add(new GraphSourcedDTO<>(graphUri, attr))); - } - if (dto.getEnumEntries() != null) { - dto.getEnumEntries().forEach(entry -> - merged.getEnumEntries().add(new GraphSourcedDTO<>(graphUri, entry))); - } - if (dto.getAssociationPairs() != null) { - dto.getAssociationPairs().forEach(assoc -> - merged.getAssociationPairs().add(new GraphSourcedDTO<>(graphUri, assoc))); + if (includeProperties) { + mergeProperties(graphUri, dto, merged, graphColor); } } } + if (doLayout) { + doDiagramLayout(diagramLayout, crossProfileDiagramUUID, mergeMap); + } + return new CrossProfileDiagramDTO( + crossProfileDiagramUUID, new ArrayList<>(mergeMap.values())); + } - diagramLayout.write(() -> { - var model = diagramLayout.getDiagramLayoutModel(); - var existingDOs = DLObjectFetcher.fetchDiagramDOs( - model, new MRID(crossProfileDiagramUUID)); - var existingClassUUIDs = existingDOs.stream() - .map(DiagramObject::getBelongsToIdentifiedObject) - .map(MRID::getUuid) - .collect(Collectors.toSet()); - - for (var merged : mergeMap.values()) { - if (!existingClassUUIDs.contains(merged.getUuid())) { - var doMRID = DiagramLayoutServiceUtils.insertDiagramObject( - model, - crossProfileDiagramUUID, - merged.getClassUri(), - merged.getUuid()); - DiagramLayoutServiceUtils.insertDiagramObjectPoint(model, doMRID); - } - } - }); + private static void mergeProperties( + String graphUri, ClassUMLAdaptedDTO dto, MergedClassDTO merged, String graphColor) { + if (dto.getAttributes() != null) { + dto.getAttributes() + .forEach( + attr -> + merged.getAttributes() + .add( + new GraphSourcedDTO<>( + graphUri, graphColor, attr))); + } + if (dto.getEnumEntries() != null) { + dto.getEnumEntries() + .forEach( + entry -> + merged.getEnumEntries() + .add( + new GraphSourcedDTO<>( + graphUri, graphColor, entry))); + } + if (dto.getAssociationPairs() != null) { + dto.getAssociationPairs() + .forEach( + assoc -> + merged.getAssociationPairs() + .add( + new GraphSourcedDTO<>( + graphUri, graphColor, assoc))); + } + if (dto.getSuperClass() != null) { + merged.getSuperClasses() + .add(new GraphSourcedDTO<>(graphUri, graphColor, dto.getSuperClass())); + } + if (dto.getStereotypes() != null) { + dto.getStereotypes() + .forEach( + stereotype -> { + if (!merged.getStereotypes() + .contains(new CIMSStereotype(stereotype))) { + merged.getStereotypes().add(new CIMSStereotype(stereotype)); + } + }); + } + } + + private static void doDiagramLayout( + DiagramLayout diagramLayout, + UUID crossProfileDiagramUUID, + Map mergeMap) { + diagramLayout.write( + () -> { + var model = diagramLayout.getDiagramLayoutModel(); + if (DLObjectFetcher.fetchDiagram(model, crossProfileDiagramUUID) == null) { + DiagramLayoutServiceUtils.insertDiagram( + model, crossProfileDiagramUUID, "CrossProfileDiagram"); + } + var existingDOs = + DLObjectFetcher.fetchDiagramDOs( + model, new MRID(crossProfileDiagramUUID)); + var existingClassUUIDs = + existingDOs.stream() + .map(DiagramObject::getBelongsToIdentifiedObject) + .map(MRID::getUuid) + .collect(Collectors.toSet()); - return new CrossProfileDiagramDTO(crossProfileDiagramUUID, new ArrayList<>(mergeMap.values())); + for (var merged : mergeMap.values()) { + if (!existingClassUUIDs.contains(merged.getUuid())) { + var doMRID = + DiagramLayoutServiceUtils.insertDiagramObject( + model, + crossProfileDiagramUUID, + merged.getClassUri(), + merged.getUuid()); + DiagramLayoutServiceUtils.insertDiagramObjectPoint(model, doMRID); + } + } + }); } @Override @@ -231,4 +292,29 @@ public void removeFromAllDiagrams(GraphIdentifier graphIdentifier, UUID classId) diagram.getClasses().removeIf(c -> c.getUuid().equals(classId)); } } + + @Override + public CrossProfileDiagramColorDataDTO getCrossProfileColors(String datasetName) { + var graphUris = databasePort.listGraphUris(datasetName); + var colorsDTO = new CrossProfileDiagramColorDataDTO(new HashMap<>()); + for (var graphUri : graphUris) { + var graphIdentifier = new GraphIdentifier(datasetName, graphUri); + var graphColor = + databasePort.getGraphWithContext(graphIdentifier).getCrossProfileDiagramColor(); + colorsDTO.getGraphColors().put(graphUri, graphColor); + } + return colorsDTO; + } + + @Override + public void replaceCrossProfileColors(String datasetName, CrossProfileDiagramColorDataDTO dto) { + var graphUris = databasePort.listGraphUris(datasetName); + for (var graphUri : graphUris) { + var graphIdentifier = new GraphIdentifier(datasetName, graphUri); + var graphWithContext = databasePort.getGraphWithContext(graphIdentifier); + if (dto.getGraphColors().containsKey(graphUri)) { + graphWithContext.setCrossProfileDiagramColor(dto.getGraphColors().get(graphUri)); + } + } + } } diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/GetCustomDiagramsUseCase.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/GetCustomDiagramsUseCase.java index 2e299df8..b53bf38c 100644 --- a/backend/src/main/java/org/rdfarchitect/services/diagrams/GetCustomDiagramsUseCase.java +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/GetCustomDiagramsUseCase.java @@ -17,11 +17,12 @@ package org.rdfarchitect.services.diagrams; -import java.util.List; import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramDTO; import org.rdfarchitect.database.GraphIdentifier; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; +import java.util.List; + public interface GetCustomDiagramsUseCase { /** @@ -46,5 +47,6 @@ public interface GetCustomDiagramsUseCase { * @param datasetName The name of the dataset. * @return The cross profile diagram for the dataset. */ - CrossProfileDiagramDTO getCrossProfileDiagram(String datasetName); + CrossProfileDiagramDTO getCrossProfileDiagram( + String datasetName, boolean includeProperties, boolean doLayout); } diff --git a/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java b/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java index ca25caec..c1527c45 100644 --- a/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java +++ b/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java @@ -42,16 +42,16 @@ public RenderingLayoutData fetchRenderingLayoutData( GraphIdentifier graphIdentifier, UUID packageUUID) { var diagramLayout = databasePort.getGraphWithContext(graphIdentifier).getDiagramLayout(); var diagramLayoutModel = diagramLayout.getDiagramLayoutModel(); - return diagramLayout.read(() -> - fetchRenderingLayoutData(diagramLayout, diagramLayoutModel, packageUUID)); + return diagramLayout.read( + () -> fetchRenderingLayoutData(diagramLayout, diagramLayoutModel, packageUUID)); } @Override public RenderingLayoutData fetchGlobalRenderingLayoutData(String datasetName, UUID diagramId) { var diagramLayout = databasePort.getDatasetDiagramLayout(datasetName); var diagramLayoutModel = diagramLayout.getDiagramLayoutModel(); - return diagramLayout.read(() -> - fetchRenderingLayoutData(diagramLayout, diagramLayoutModel, diagramId)); + return diagramLayout.read( + () -> fetchRenderingLayoutData(diagramLayout, diagramLayoutModel, diagramId)); } private RenderingLayoutData fetchRenderingLayoutData( diff --git a/backend/src/main/java/org/rdfarchitect/services/dl/update/classlayout/UpdateClassLayoutService.java b/backend/src/main/java/org/rdfarchitect/services/dl/update/classlayout/UpdateClassLayoutService.java index 28e205ef..528a846e 100644 --- a/backend/src/main/java/org/rdfarchitect/services/dl/update/classlayout/UpdateClassLayoutService.java +++ b/backend/src/main/java/org/rdfarchitect/services/dl/update/classlayout/UpdateClassLayoutService.java @@ -84,7 +84,10 @@ public void updateClassPositions( var resolvedPackageUUID = packageUUID != null ? packageUUID : diagramLayout.getDefaultPackageMRID().getUuid(); - diagramLayout.write(() -> updateDiagramObjects(resolvedPackageUUID, classPositionDTOList, diagramLayoutModel)); + diagramLayout.write( + () -> + updateDiagramObjects( + resolvedPackageUUID, classPositionDTOList, diagramLayoutModel)); } @Override @@ -93,7 +96,8 @@ public void updateClassPositions( var diagramLayout = databasePort.getDatasetDiagramLayout(datasetName); var diagramLayoutModel = diagramLayout.getDiagramLayoutModel(); - diagramLayout.write(() -> updateDiagramObjects(diagramUUID, classPositionDTOList, diagramLayoutModel)); + diagramLayout.write( + () -> updateDiagramObjects(diagramUUID, classPositionDTOList, diagramLayoutModel)); } private void updateDiagramObjects( diff --git a/backend/src/main/java/org/rdfarchitect/services/rendering/DiagramToCIMCollectionConverterService.java b/backend/src/main/java/org/rdfarchitect/services/rendering/DiagramToCIMCollectionConverterService.java index 5325d046..fe24f2b2 100644 --- a/backend/src/main/java/org/rdfarchitect/services/rendering/DiagramToCIMCollectionConverterService.java +++ b/backend/src/main/java/org/rdfarchitect/services/rendering/DiagramToCIMCollectionConverterService.java @@ -19,13 +19,24 @@ import lombok.RequiredArgsConstructor; +import org.rdfarchitect.api.dto.association.AssociationPairMapper; +import org.rdfarchitect.api.dto.attributes.AttributeMapper; +import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramDTO; +import org.rdfarchitect.api.dto.crossProfileDiagram.MergedClassDTO; +import org.rdfarchitect.api.dto.enumentries.EnumEntryMapper; import org.rdfarchitect.database.DatabasePort; import org.rdfarchitect.database.GraphIdentifier; import org.rdfarchitect.database.inmemory.diagrams.ClassInDiagram; import org.rdfarchitect.models.cim.data.dto.CIMCollection; +import org.rdfarchitect.models.cim.data.dto.CIMMergedClass; +import org.rdfarchitect.models.cim.data.dto.relations.RDFSLabel; +import org.rdfarchitect.models.cim.data.dto.relations.RDFSSubClassOf; +import org.rdfarchitect.models.cim.data.dto.relations.RDFType; +import org.rdfarchitect.models.cim.data.dto.relations.uri.URI; import org.rdfarchitect.models.cim.rendering.GraphFilter; import org.springframework.stereotype.Service; +import java.util.ArrayList; import java.util.UUID; import java.util.stream.Collectors; @@ -38,6 +49,10 @@ public class DiagramToCIMCollectionConverterService private final GraphToCIMCollectionConverterService converter; + private final AttributeMapper attributeMapper; + private final EnumEntryMapper enumEntryMapper; + private final AssociationPairMapper associationPairMapper; + @Override public CIMCollection convert(GraphIdentifier graphIdentifier, String diagramId) { var graphWithContext = databasePort.getGraphWithContext(graphIdentifier); @@ -97,4 +112,82 @@ private void mergeInto(CIMCollection target, CIMCollection source) { target.getEnumEntries().addAll(source.getEnumEntries()); target.getAssociations().addAll(source.getAssociations()); } + + @Override + public CIMCollection convert(CrossProfileDiagramDTO diagram) { + var collection = new CIMCollection(); + + for (var mergedClass : diagram.getClasses()) { + var uri = new URI(mergedClass.getClassUri()); + var superClasses = new ArrayList(); + for (var superClassDTO : mergedClass.getSuperClasses()) { + var superClass = superClassDTO.getValue(); + superClasses.add( + new RDFSSubClassOf( + new URI(superClass.getPrefix() + superClass.getLabel()), + new RDFSLabel(superClass.getLabel()))); + } + var label = new RDFSLabel(uri.getSuffix()); + var cimClass = + CIMMergedClass.builder() + .uuid(mergedClass.getUuid()) + .uri(uri) + .label(label) + .superClasses(superClasses) + .stereotypes(mergedClass.getStereotypes()) + .build(); + collection.getClasses().add(cimClass); + + convertAttributes(mergedClass, collection); + convertEnumEntries(mergedClass, collection); + convertAssociationPairs(mergedClass, collection); + } + + return collection; + } + + private void convertAssociationPairs(MergedClassDTO mergedClass, CIMCollection collection) { + for (var graphSourcedPair : mergedClass.getAssociationPairs()) { + var pair = associationPairMapper.toCIMObject(graphSourcedPair.getValue()); + var graphUri = graphSourcedPair.getGraphUri(); + if (pair.getFrom() != null) { + collection + .getAssociations() + .add(pair.getFrom().toBuilder().graphUri(graphUri).build()); + } + if (pair.getTo() != null) { + collection + .getAssociations() + .add(pair.getTo().toBuilder().graphUri(graphUri).build()); + } + } + } + + private void convertEnumEntries(MergedClassDTO mergedClass, CIMCollection collection) { + for (var graphSourcedEntry : mergedClass.getEnumEntries()) { + var cimEnumEntry = enumEntryMapper.toCIMObject(graphSourcedEntry.getValue()); + cimEnumEntry = + cimEnumEntry.toBuilder() + .type( + new RDFType( + new URI(mergedClass.getClassUri()), + new RDFSLabel(graphSourcedEntry.getValue().getLabel()))) + .graphUri(graphSourcedEntry.getGraphUri()) + .color(graphSourcedEntry.getGraphColor()) + .build(); + collection.getEnumEntries().add(cimEnumEntry); + } + } + + private void convertAttributes(MergedClassDTO mergedClass, CIMCollection collection) { + for (var graphSourcedAttr : mergedClass.getAttributes()) { + var cimAttribute = attributeMapper.toCIMObject(graphSourcedAttr.getValue()); + cimAttribute = + cimAttribute.toBuilder() + .graphUri(graphSourcedAttr.getGraphUri()) + .color(graphSourcedAttr.getGraphColor()) + .build(); + collection.getAttributes().add(cimAttribute); + } + } } diff --git a/backend/src/main/java/org/rdfarchitect/services/rendering/DiagramToCIMCollectionConverterUseCase.java b/backend/src/main/java/org/rdfarchitect/services/rendering/DiagramToCIMCollectionConverterUseCase.java index 8302a191..b2218219 100644 --- a/backend/src/main/java/org/rdfarchitect/services/rendering/DiagramToCIMCollectionConverterUseCase.java +++ b/backend/src/main/java/org/rdfarchitect/services/rendering/DiagramToCIMCollectionConverterUseCase.java @@ -17,6 +17,7 @@ package org.rdfarchitect.services.rendering; +import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramDTO; import org.rdfarchitect.database.GraphIdentifier; import org.rdfarchitect.models.cim.data.dto.CIMCollection; @@ -40,4 +41,12 @@ public interface DiagramToCIMCollectionConverterUseCase { * @return The {@link CIMCollection}. */ CIMCollection convert(String datasetName, String diagramId); + + /** + * Converts a Diagram to a {@link CIMCollection}. + * + * @param diagram The diagram to be converted. + * @return The {@link CIMCollection}. + */ + CIMCollection convert(CrossProfileDiagramDTO diagram); } diff --git a/backend/src/main/java/org/rdfarchitect/services/rendering/GraphToCIMCollectionConverterService.java b/backend/src/main/java/org/rdfarchitect/services/rendering/GraphToCIMCollectionConverterService.java index 21e36688..8b60e353 100644 --- a/backend/src/main/java/org/rdfarchitect/services/rendering/GraphToCIMCollectionConverterService.java +++ b/backend/src/main/java/org/rdfarchitect/services/rendering/GraphToCIMCollectionConverterService.java @@ -165,10 +165,12 @@ private void fetchClasses( CIMCollection cimCollection) { if (filter.getAllowedUUIDs() != null && !filter.getAllowedUUIDs().isEmpty()) { fetchSpecifiedClasses(graph, graphIdentifier, filter, cimCollection); - } else { + } else if (filter.getPackageUUID() != null && !filter.getPackageUUID().equals("default")) { fetchClassesInPackage(graph, graphIdentifier, filter, cimCollection); fetchExternalAssociatedClasses(graph, graphIdentifier, filter, cimCollection); fetchExternallyInheritanceRelatedClasses(graph, graphIdentifier, filter, cimCollection); + } else { + fetchAllClasses(graph, graphIdentifier, filter, cimCollection); } clearSuperClassRelations(filter, cimCollection); @@ -176,6 +178,18 @@ private void fetchClasses( clearInheritanceToNonExistingClasses(cimCollection); } + private void fetchAllClasses( + Graph graph, + GraphIdentifier graphIdentifier, + GraphFilter filter, + CIMCollection cimCollection) { + var classQueryBuilder = + CIMQueries.getClassesQuery( + databasePort.getPrefixMapping(graphIdentifier.datasetName()), + graphIdentifier.graphUri()); + fetchClasses(graph, graphIdentifier, filter, cimCollection, classQueryBuilder); + } + private void clearSuperClassRelations(GraphFilter filter, CIMCollection cimCollection) { if (filter.isIncludeInheritance()) { return; @@ -375,7 +389,12 @@ private void fetchAttributes( databasePort.getPrefixMapping(graphIdentifier.datasetName())) .fetchCIMAttributeList(attributesQuery.build()); - attributeList.forEach(cimAttribute -> cimCollection.getAttributes().add(cimAttribute)); + attributeList.forEach( + cimAttribute -> { + cimAttribute = + cimAttribute.toBuilder().graphUri(graphIdentifier.graphUri()).build(); + cimCollection.getAttributes().add(cimAttribute); + }); } // enums @@ -463,7 +482,12 @@ private void fetchEnumEntries( databasePort.getPrefixMapping(graphIdentifier.datasetName())) .fetchCIMEnumEntryList(enumEntriesQuery.build()); - enumEntryList.forEach(cimEnumEntry -> cimCollection.getEnumEntries().add(cimEnumEntry)); + enumEntryList.forEach( + cimEnumEntry -> { + cimEnumEntry = + cimEnumEntry.toBuilder().graphUri(graphIdentifier.graphUri()).build(); + cimCollection.getEnumEntries().add(cimEnumEntry); + }); } private void fetchAssociations( @@ -502,11 +526,19 @@ private void fetchAssociations( associationList.forEach( cimAssociation -> { + var finalCimAssociation = cimAssociation; if (cimCollection.getClasses().stream() .anyMatch( cimClass -> cimClass.getUri() - .equals(cimAssociation.getRange().getUri()))) { + .equals( + finalCimAssociation + .getRange() + .getUri()))) { + cimAssociation = + cimAssociation.toBuilder() + .graphUri(graphIdentifier.graphUri()) + .build(); cimCollection.getAssociations().add(cimAssociation); } }); diff --git a/backend/src/main/java/org/rdfarchitect/services/select/QueryGraphService.java b/backend/src/main/java/org/rdfarchitect/services/select/QueryGraphService.java index e617c27c..28641a98 100644 --- a/backend/src/main/java/org/rdfarchitect/services/select/QueryGraphService.java +++ b/backend/src/main/java/org/rdfarchitect/services/select/QueryGraphService.java @@ -200,15 +200,17 @@ public List getFullClassList(GraphIdentifier graphIdentifier try { return baseList.stream() .filter(dto -> dto.getUuid() != null) - .map(dto -> { - var fullClass = CIMUMLObjectFactory.createCIMClassUMLAdapted( - rdfGraph, - graphIdentifier.graphUri(), - prefixMapping, - dto.getUuid().toString()); - fullClass.nullEmptyLists(); - return classMapper.toDTO(fullClass); - }) + .map( + dto -> { + var fullClass = + CIMUMLObjectFactory.createCIMClassUMLAdapted( + rdfGraph, + graphIdentifier.graphUri(), + prefixMapping, + dto.getUuid().toString()); + fullClass.nullEmptyLists(); + return classMapper.toDTO(fullClass); + }) .toList(); } finally { rdfGraph.end(); diff --git a/backend/src/test/java/org/rdfarchitect/services/diagrams/CustomDiagramsServiceTest.java b/backend/src/test/java/org/rdfarchitect/services/diagrams/CustomDiagramsServiceTest.java index a341b118..6ebbb1d1 100644 --- a/backend/src/test/java/org/rdfarchitect/services/diagrams/CustomDiagramsServiceTest.java +++ b/backend/src/test/java/org/rdfarchitect/services/diagrams/CustomDiagramsServiceTest.java @@ -29,6 +29,7 @@ import org.rdfarchitect.database.inmemory.diagrams.ClassInDiagram; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.models.cim.data.dto.relations.uri.URI; +import org.rdfarchitect.services.select.GetClassListUseCase; import java.util.ArrayList; import java.util.List; @@ -45,7 +46,8 @@ class CustomDiagramsServiceTest { @BeforeEach void setUp() { databasePort = mock(DatabasePort.class); - service = new CustomDiagramService(databasePort); + var getClassListUseCase = mock(GetClassListUseCase.class); + service = new CustomDiagramService(databasePort, getClassListUseCase); graphIdentifier = mock(GraphIdentifier.class); when(graphIdentifier.graphUri()).thenReturn("http://example.org#graph"); diff --git a/frontend/src/lib/api/apiDatasetUtils.js b/frontend/src/lib/api/apiDatasetUtils.js index 847ca615..e2a1aaf9 100644 --- a/frontend/src/lib/api/apiDatasetUtils.js +++ b/frontend/src/lib/api/apiDatasetUtils.js @@ -48,3 +48,9 @@ export async function getDatasetNames() { } return { modifiable: modifiableDatasets, readonly: readOnlyDatasets }; } + +export async function getCrossProfileDiagram(datasetName) { + if (!datasetName) return null; + const res = await bec.getCrossProfileDiagramForDataset(datasetName); + return await res.json(); +} diff --git a/frontend/src/lib/api/backend.js b/frontend/src/lib/api/backend.js index 8e18ceb0..86f9e995 100644 --- a/frontend/src/lib/api/backend.js +++ b/frontend/src/lib/api/backend.js @@ -46,6 +46,16 @@ export class BackendConnection { }); } + async getCrossProfileID(datasetName) { + const url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/crossprofilediagramID`; + return fetch(url, { + method: "GET", + mode: "cors", + headers: new Headers({ "Content-Type": "application/json" }), + credentials: "include", + }); + } + async getGraphNames(datasetName) { const url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs`; return fetch(url, { @@ -558,6 +568,16 @@ export class BackendConnection { }); } + async getCrossProfileDiagramRenderingDataForDataset(datasetName) { + let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/crossprofilediagramRendering`; + return await fetch(url, { + method: "GET", + mode: "cors", + headers: new Headers({ "Content-Type": "application/json" }), + credentials: "include", + }); + } + async getCrossProfileDiagramForDataset(datasetName) { let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/crossprofilediagram`; return await fetch(url, { @@ -568,6 +588,27 @@ export class BackendConnection { }); } + async getCrossProfileColorData(datasetName) { + let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/crossprofilediagramColors`; + return await fetch(url, { + method: "GET", + mode: "cors", + headers: new Headers({ "Content-Type": "application/json" }), + credentials: "include", + }); + } + + async putCrossProfileColorData(datasetName, colorData) { + let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/crossprofilediagramColors`; + return await fetch(url, { + method: "PUT", + mode: "cors", + headers: new Headers({ "Content-Type": "application/json" }), + body: JSON.stringify(colorData), + credentials: "include", + }); + } + async putCustomDiagram(datasetName, graphURI, diagramId, newDiagram) { let url = `${PUBLIC_BACKEND_URL}/datasets/${encodeURIComponent(datasetName)}/graphs/${encodeURIComponent(graphURI)}/diagrams/${encodeURIComponent(diagramId)}`; return await fetch(url, { diff --git a/frontend/src/lib/rendering/svelteflow/CrossProfileDiagram.svelte b/frontend/src/lib/rendering/svelteflow/CrossProfileDiagram.svelte deleted file mode 100644 index 23eb3f65..00000000 --- a/frontend/src/lib/rendering/svelteflow/CrossProfileDiagram.svelte +++ /dev/null @@ -1,159 +0,0 @@ - - - - -
- - - -
- {#if !error} - - {/if} -
- - {#if isLoading || isFlowLoading} -
- -
- {:else if error} -
-

{error}

-
- {/if} -
- - - {#if selectedMergedClass} - {#key classEditorKey} - - {/key} - {/if} - -
-
-
diff --git a/frontend/src/lib/rendering/svelteflow/components/ClassNode.svelte b/frontend/src/lib/rendering/svelteflow/components/ClassNode.svelte index 8edb24f0..0eba0a1b 100644 --- a/frontend/src/lib/rendering/svelteflow/components/ClassNode.svelte +++ b/frontend/src/lib/rendering/svelteflow/components/ClassNode.svelte @@ -18,7 +18,7 @@
{#if attributes && attributes.length > 0} - {#each attributes as attr} -
- {attr.label}: {attr.type}  [{attr.multiplicity}] -
- {/each} + {#if isCrossProfileDiagram} + {#each groupByGraphURI(attributes) as group} +
+ {group.graphName} +
+ {#each group.props as attr} +
+ {attr.label}: {attr.type}  [{attr.multiplicity}] +
+ {/each} + {/each} + {:else} + {#each attributes as attr} +
+ {attr.label}: {attr.type}  [{attr.multiplicity}] +
+ {/each} + {/if} {:else if enumEntries && enumEntries.length > 0} - {#each enumEntries as enumEntry} -
- {enumEntry} -
- {/each} + {#if isCrossProfileDiagram} + {#each groupByGraphURI(enumEntries) as group} +
+ {group.graphName} +
+ {#each group.props as enumEntry} +
+ {enumEntry.label ?? enumEntry} +
+ {/each} + {/each} + {:else} + {#each enumEntries as enumEntry} +
+ {enumEntry.label ?? enumEntry} +
+ {/each} + {/if} {/if}
diff --git a/frontend/src/lib/rendering/svelteflow/components/SvelteFlowClassContextMenu.svelte b/frontend/src/lib/rendering/svelteflow/components/SvelteFlowClassContextMenu.svelte index 2e90ecfd..16351f47 100644 --- a/frontend/src/lib/rendering/svelteflow/components/SvelteFlowClassContextMenu.svelte +++ b/frontend/src/lib/rendering/svelteflow/components/SvelteFlowClassContextMenu.svelte @@ -31,7 +31,11 @@ import { ContextMenu } from "$lib/components/bitsui/contextmenu"; import ContextMenuSeparator from "$lib/components/bitsui/contextmenu/ContextMenuSeparator.svelte"; - import { copyState, editorState } from "$lib/sharedState.svelte.js"; + import { + copyState, + DiagramType, + editorState, + } from "$lib/sharedState.svelte.js"; import { getContextMenuTriggerStyle, @@ -169,106 +173,108 @@ style={triggerStyle} {disabled} /> - - - Copy - - - Extend Class - - - { - showSHACLDialog = true; - }} - faIcon={faDiagramProject} - > - Constraints - - - + - Move - - - { - e.preventDefault(); - handleMoveToTop(); - }} - faIcon={faAnglesUp} - disabled={classActionsDisabled || isAtFront} - > - Move to front - - { - e.preventDefault(); - handleMoveUp(); - }} - faIcon={faAngleUp} - disabled={classActionsDisabled || isAtFront} - > - Move up - - + + Extend Class + + + { + showSHACLDialog = true; + }} + faIcon={faDiagramProject} + > + Constraints + + + - Layer - - { - e.preventDefault(); - handleMoveDown(); - }} - faIcon={faAngleDown} - disabled={classActionsDisabled || isAtBack} - > - Move down - - { - e.preventDefault(); - handleMoveToBottom(); - }} - faIcon={faAnglesDown} - disabled={classActionsDisabled || isAtBack} - > - Move to bottom - - - - - - Remove from Diagram - - - Delete class - - + Move + + + { + e.preventDefault(); + handleMoveToTop(); + }} + faIcon={faAnglesUp} + disabled={classActionsDisabled || isAtFront} + > + Move to front + + { + e.preventDefault(); + handleMoveUp(); + }} + faIcon={faAngleUp} + disabled={classActionsDisabled || isAtFront} + > + Move up + + + Layer + + { + e.preventDefault(); + handleMoveDown(); + }} + faIcon={faAngleDown} + disabled={classActionsDisabled || isAtBack} + > + Move down + + { + e.preventDefault(); + handleMoveToBottom(); + }} + faIcon={faAnglesDown} + disabled={classActionsDisabled || isAtBack} + > + Move to bottom + + + + + + Remove from Diagram + + + Delete class + + + {/if} - - - Add class - - {#if editorState.selectedDiagram.getProperty("type") === DiagramType.PACKAGE} + {#if editorState.selectedDiagram.getProperty("type") === DiagramType.PACKAGE} + + + Add class + + @@ -172,8 +173,8 @@ - {/if} - + + {/if} [c.classUri, c.uuid])); - - const nodes = dto.classes.map(mergedClass => { - console.log("attributes raw:", mergedClass.attributes); - console.log("enumEntries raw:", mergedClass.enumEntries); - return { - id: mergedClass.uuid, - type: "class", - position: { x: 0, y: 0 }, - data: { - label: extractLabel(mergedClass.classUri), - stereotypes: [], - attributes: mergedClass.attributes.map(graphSourced => ({ - label: graphSourced.value.label ?? "", - type: graphSourced.value.dataType - ? (graphSourced.value.dataType.label ?? "") - : "", - multiplicity: graphSourced.value.multiplicity ?? "", - })), - enumEntries: mergedClass.enumEntries.map( - graphSourced => graphSourced.value.label ?? "", - ), - belongsToCategory: null, - graphUri: mergedClass.sources[0]?.graphUri ?? null, - }, - }; - }); - - const edges = buildEdges(dto.classes, uuidByClassUri); - - return { nodes, edges }; -} - -function extractLabel(classUri) { - const hash = classUri.lastIndexOf("#"); - const slash = classUri.lastIndexOf("/"); - const idx = Math.max(hash, slash); - return idx >= 0 ? classUri.substring(idx + 1) : classUri; -} - -function buildEdges(classes, uuidByClassUri) { - const edges = []; - const edgeIds = new Set(); - - for (const mergedClass of classes) { - for (const graphSourced of mergedClass.associationPairs) { - const pair = graphSourced.value; - const targetPrefix = pair.to?.range?.prefix ?? ""; - const targetLabel = pair.to?.range?.label ?? ""; - const targetClassUri = targetPrefix + targetLabel; - const targetUuid = uuidByClassUri.get(targetClassUri); - - if (!targetUuid) continue; - - const edgeId = `${mergedClass.uuid}→${pair.from?.uuid ?? pair.to?.uuid}→${targetUuid}`; - if (edgeIds.has(edgeId)) continue; - edgeIds.add(edgeId); - - edges.push({ - id: edgeId, - source: mergedClass.uuid, - target: targetUuid, - type: "association", - data: { - label: pair.from?.label ?? "", - multiplicity: pair.to?.multiplicity ?? "", - }, - }); - } - } - - return edges; -} diff --git a/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte b/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte index 202e4f42..0d5e7cd9 100644 --- a/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte +++ b/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte @@ -32,6 +32,8 @@ import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; import { eventStack } from "$lib/eventhandling/closeEventManager.svelte.js"; import { + ClassType, + DiagramType, editorState, forceReloadTrigger, } from "$lib/sharedState.svelte.js"; @@ -292,12 +294,24 @@ editorState.selectedClassGraph.updateValue( nodeClickEvent.node.data.graphUri, ); + editorState.selectedClassType.updateValue( + editorState.selectedDiagram.getProperty("type") === + DiagramType.CROSS_PROFILE + ? ClassType.MERGED_CLASS + : ClassType.NORMAL_CLASS, + ); editorState.selectedClassUUID.updateValue(id); + console.log(editorState.selectedClassType.getValue()); } else { eventStack.executeNewestEvent({ datasetName: editorState.selectedDataset.getValue(), graphUri: nodeClickEvent.node.data.graphUri, classUuid: id, + classType: + editorState.selectedDiagram.getProperty("type") === + DiagramType.CROSS_PROFILE + ? ClassType.MERGED_CLASS + : ClassType.NORMAL_CLASS, }); } diff --git a/frontend/src/lib/sharedState.svelte.js b/frontend/src/lib/sharedState.svelte.js index e75f6549..156afec9 100644 --- a/frontend/src/lib/sharedState.svelte.js +++ b/frontend/src/lib/sharedState.svelte.js @@ -41,6 +41,11 @@ export const DiagramType = { PACKAGE: "package", }; +export const ClassType = { + NORMAL_CLASS: "normalClass", + MERGED_CLASS: "mergedClass", +}; + /** * The editorState object contains the state of the editor. Content might expand in the future. * @type {{ @@ -62,6 +67,7 @@ export const editorState = { selectedDiagram: new StateObjectPair({ type: null, id: null }), selectedClassDataset: new StateValuePair(), selectedClassGraph: new StateValuePair(), + selectedClassType: new StateValuePair(), selectedClassUUID: new StateValuePair(), focusedClassUUID: new StateValuePair(), selectedContext: new StateValuePair(), @@ -72,6 +78,7 @@ export const editorState = { this.selectedDiagram.updateValue({ type: null, id: null }); this.selectedClassDataset.updateValue(null); this.selectedClassGraph.updateValue(null); + this.selectedClassType.updateValue(null); this.selectedClassUUID.updateValue(null); this.focusedClassUUID.updateValue(null); this.selectedContext.updateValue(null); diff --git a/frontend/src/routes/GraphDeleteDialog.svelte b/frontend/src/routes/GraphDeleteDialog.svelte index 1118de46..85353a11 100644 --- a/frontend/src/routes/GraphDeleteDialog.svelte +++ b/frontend/src/routes/GraphDeleteDialog.svelte @@ -68,6 +68,7 @@ }); editorState.selectedClassDataset.updateValue(null); editorState.selectedClassGraph.updateValue(null); + editorState.selectedClassType.updateValue(null); editorState.selectedClassUUID.updateValue(null); toastStore.success( "Schema deleted", diff --git a/frontend/src/routes/ImportDialog.svelte b/frontend/src/routes/ImportDialog.svelte index 0fd07614..0cb87667 100644 --- a/frontend/src/routes/ImportDialog.svelte +++ b/frontend/src/routes/ImportDialog.svelte @@ -241,6 +241,7 @@ editorState.selectedDiagram.updateValue({ type: null, id: null }); editorState.selectedClassDataset.updateValue(null); editorState.selectedClassGraph.updateValue(null); + editorState.selectedClassType.updateValue(null); editorState.selectedClassUUID.updateValue(null); const importedCount = importedGraphUris.length; diff --git a/frontend/src/routes/NewClassDialog.svelte b/frontend/src/routes/NewClassDialog.svelte index ca6465d8..8f5fff55 100644 --- a/frontend/src/routes/NewClassDialog.svelte +++ b/frontend/src/routes/NewClassDialog.svelte @@ -33,6 +33,7 @@ import { getPackageDisplayLabel } from "$lib/utils/package-label.js"; import { + ClassType, DiagramType, editorState, forceReloadTrigger, @@ -239,6 +240,9 @@ }); editorState.selectedClassDataset.updateValue(datasetNameLocal); editorState.selectedClassGraph.updateValue(graphURILocal); + editorState.selectedClassType.updateValue( + ClassType.NORMAL_CLASS, + ); editorState.selectedClassUUID.updateValue(uuid); toastStore.success( "Class created", diff --git a/frontend/src/routes/Searchbar.svelte b/frontend/src/routes/Searchbar.svelte index c2aa1103..03cc1000 100644 --- a/frontend/src/routes/Searchbar.svelte +++ b/frontend/src/routes/Searchbar.svelte @@ -27,6 +27,7 @@ import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; import { toastStore } from "$lib/eventhandling/toastStore.svelte.js"; import { + ClassType, DiagramType, editorState, forceReloadTrigger, @@ -59,6 +60,7 @@ searchResult.datasetName, ); editorState.selectedClassGraph.updateValue(searchResult.graphUri); + editorState.selectedClassType.updateValue(ClassType.NORMAL_CLASS); editorState.selectedClassUUID.updateValue(searchResult.uuid); editorState.focusedClassUUID.updateValue(searchResult.uuid); } else if (searchResult.type === "PACKAGE") { diff --git a/frontend/src/routes/delete-relations-dialog/DeleteDependenciesDialog.svelte b/frontend/src/routes/delete-relations-dialog/DeleteDependenciesDialog.svelte index dcb4a4e3..121e4dee 100644 --- a/frontend/src/routes/delete-relations-dialog/DeleteDependenciesDialog.svelte +++ b/frontend/src/routes/delete-relations-dialog/DeleteDependenciesDialog.svelte @@ -162,6 +162,7 @@ forceReloadTrigger.trigger(); editorState.selectedClassDataset.updateValue(null); editorState.selectedClassGraph.updateValue(null); + editorState.selectedClassType.updateValue(null); editorState.selectedClassUUID.updateValue(null); toastStore.success( `${type ? type.charAt(0).toUpperCase() + type.slice(1) : "Resource"} deleted`, diff --git a/frontend/src/routes/mainpage/classEditor/classEditor.svelte b/frontend/src/routes/mainpage/classEditor/classEditor.svelte index e8b33a87..a3a35fb2 100644 --- a/frontend/src/routes/mainpage/classEditor/classEditor.svelte +++ b/frontend/src/routes/mainpage/classEditor/classEditor.svelte @@ -83,6 +83,7 @@ let datasetOfClassToOpenNext = $state(null); let graphOfClassToOpenNext = $state(null); let classToOpenNext = $state(null); + let classTypeOfClassToOpenNext = $state(null); let isEnum = $derived( reactiveClass?.stereotypes.contains(enumerationStereotype), @@ -117,10 +118,16 @@ onDestroy(() => eventStack.removeEvent(closeClassEditor)); function closeClassEditor( - { datasetName = null, graphUri = null, classUuid = null } = { + { + datasetName = null, + graphUri = null, + classUuid = null, + classType = null, + } = { datasetName: null, graphUri: null, classUuid: null, + classType: null, }, ) { if (!showDiscardSaveConfirmDialog && reactiveClass?.isModified) { @@ -128,10 +135,12 @@ datasetOfClassToOpenNext = datasetName; graphOfClassToOpenNext = graphUri; classToOpenNext = classUuid; + classTypeOfClassToOpenNext = classType; return; } editorState.selectedClassDataset.updateValue(datasetName); editorState.selectedClassGraph.updateValue(graphUri); + editorState.selectedClassType.updateValue(classType); editorState.selectedClassUUID.updateValue(classUuid); } @@ -290,6 +299,7 @@ {datasetOfClassToOpenNext} {graphOfClassToOpenNext} {classToOpenNext} + {classTypeOfClassToOpenNext} {closeClassEditor} />
{ saveChanges(); editorState.selectedClassDataset.updateValue(datasetOfClassToOpenNext); editorState.selectedClassGraph.updateValue(graphOfClassToOpenNext); + editorState.selectedClassType.updateValue(classTypeOfClassToOpenNext); editorState.selectedClassUUID.updateValue(classToOpenNext); }} disableSave={!reactiveClass?.isModified || !reactiveClass?.isValid} diff --git a/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte b/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte index b7f75b6b..0770bf07 100644 --- a/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte +++ b/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte @@ -16,17 +16,36 @@ --> -{#if mergedClass && mergedClass.sources?.length > 0} -
- {#each mergedClass.sources as source, i} - - {/each} +{#if loading} +
+
+ +
+
+{:else if mergedClass && mergedClass.sources?.length > 0} +
+
{#if activeSource} diff --git a/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte b/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte index 562f186a..b52a37aa 100644 --- a/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte @@ -34,6 +34,7 @@ DiagramType, copyState, editorState, + ClassType, } from "$lib/sharedState.svelte.js"; import { shortenIri } from "$lib/utils/iri.js"; @@ -75,9 +76,11 @@ } onPackChange(); if (!editorState.selectedClassUUID.getValue()) { + console.warn("here: ", { diagramId }); eventStack.executeNewestEvent(classNavEntry.id); editorState.selectedClassDataset.updateValue(datasetNavEntry.id); editorState.selectedClassGraph.updateValue(graphNavEntry.id); + editorState.selectedClassType.updateValue(ClassType.NORMAL_CLASS); editorState.selectedClassUUID.updateValue(classNavEntry.id); return; } diff --git a/frontend/src/routes/mainpage/packageNavigation/CrossProfileDiagramsSection.svelte b/frontend/src/routes/mainpage/packageNavigation/CrossProfileDiagramsSection.svelte new file mode 100644 index 00000000..b557c853 --- /dev/null +++ b/frontend/src/routes/mainpage/packageNavigation/CrossProfileDiagramsSection.svelte @@ -0,0 +1,78 @@ + + + + + + + + + { + editorState.selectedDataset.updateValue(datasetNavEntry.label); + editorState.selectedGraph.updateValue(null); + editorState.selectedDiagram.updateValue({ + type: DiagramType.CROSS_PROFILE, + id: datasetNavEntry.crossProfileID, + }); + }} + /> + + + { + showColorDialog = true; + }} + faIcon={faFileExport} + > + Edit Schema Colors + + + + + diff --git a/frontend/src/routes/mainpage/packageNavigation/DatasetSection.svelte b/frontend/src/routes/mainpage/packageNavigation/DatasetSection.svelte index fc0b99a7..77423a23 100644 --- a/frontend/src/routes/mainpage/packageNavigation/DatasetSection.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/DatasetSection.svelte @@ -26,24 +26,24 @@ faLock, faDiagramProject, faPlus, - faShareNodes, } from "@fortawesome/free-solid-svg-icons"; - import { getContext } from "svelte"; + import { getContext, onMount } from "svelte"; import { enableEditing, disableEditing, } from "$lib/actions/editingActions.js"; import { getNamespaces, isReadOnly } from "$lib/api/apiDatasetUtils.js"; + import { BackendConnection } from "$lib/api/backend.js"; import { ContextMenu } from "$lib/components/bitsui/contextmenu"; import NavigationEntry from "$lib/components/navigation/NavigationEntry.svelte"; - import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; + import { PUBLIC_BACKEND_URL } from "$lib/config/runtime.js"; import { - DiagramType, editorState, forceReloadTrigger, } from "$lib/sharedState.svelte.js"; + import CrossProfileDiagramsSection from "./CrossProfileDiagramsSection.svelte"; import CustomDiagramsSection from "./CustomDiagramsSection.svelte"; import GraphSection from "./GraphSection.svelte"; import { isSelectedDataset } from "./packageNavigationUtils.svelte.js"; @@ -56,6 +56,8 @@ let { datasetNavEntry } = $props(); + const bec = new BackendConnection(fetch, PUBLIC_BACKEND_URL); + let showImportDialog = $state(false); let showNewGraphDialog = $state(false); let showNewDiagramDialog = $state(false); @@ -83,6 +85,11 @@ wasDatasetSelected = isDatasetSelected; }); + onMount(async () => { + let res = await bec.getCrossProfileID(datasetNavEntry.label); + datasetNavEntry.crossProfileID = await res.text(); + }); + async function fetchNamespaces() { if (!datasetNavEntry?.label) { namespaces = []; @@ -238,33 +245,7 @@ /> {/each} - - { - editorState.selectedDataset.updateValue( - datasetNavEntry.label, - ); - editorState.selectedGraph.updateValue(null); - editorState.selectedClassUUID.updateValue(null); - editorState.selectedDiagram.updateValue({ - type: DiagramType.CROSS_PROFILE, - id: null, - }); - }} - /> + + + + + +
+
+ {#if colorEntries.length === 0} +

+ No graphs available for this dataset. +

+ {:else} +

+ Assign a color to each schema. The color is used in the + merged view. +

+ +
+ {#each colorEntries as entry (entry.graphURI)} +
+ + +
+

+ {shortName(entry.graphURI)} +

+

+ {entry.graphURI} +

+
+ + +
+ {/each} +
+ {/if} +
+
+
diff --git a/frontend/src/routes/mainpage/packageWindow.svelte b/frontend/src/routes/mainpage/packageWindow.svelte index a223e723..31a1f708 100644 --- a/frontend/src/routes/mainpage/packageWindow.svelte +++ b/frontend/src/routes/mainpage/packageWindow.svelte @@ -18,10 +18,10 @@
- {#if isCrossProfile && selectedDataset} - {#key selectedDataset} - - {/key} - {:else} - + - - {#key renderingKey} -
- -
- {/key} -
+ {#key renderingKey} +
+ +
+ {/key} +
- - {#if isClassSelected} - {#key classEditorKey} + + {#if isClassSelected} + {#key classEditorKey} + {#if isMergedClass} + + {:else} - {/key} - {/if} - -
- {/if} + {/if} + {/key} + {/if} + +
diff --git a/frontend/src/routes/mainpage/renderingWrapper.svelte b/frontend/src/routes/mainpage/renderingWrapper.svelte index 22f92e8d..fa2a9a5c 100644 --- a/frontend/src/routes/mainpage/renderingWrapper.svelte +++ b/frontend/src/routes/mainpage/renderingWrapper.svelte @@ -98,6 +98,8 @@ await fetchGraphDiagramRenderingData(diagramId); } else if (diagramType === DiagramType.CUSTOM_DATASET_DIAGRAM) { await fetchDatasetDiagramRenderingData(diagramId); + } else if (diagramType === DiagramType.CROSS_PROFILE) { + await fetchCrossProfileRenderingData(); } else { await fetchPackageRenderingData( datasetName, @@ -207,6 +209,31 @@ } } + async function fetchCrossProfileRenderingData() { + console.log("fetchCrossProfileRenderingData"); + try { + const res = await bec.getCrossProfileDiagramRenderingDataForDataset( + editorState.selectedDataset.getValue(), + ); + + const responseText = await res.text(); + if (!responseText) { + displayDiagram = false; + } else { + response = JSON.parse(responseText); + renderingFormat = response.format; + displayDiagram = true; + console.log("fetched crossProfileDIagram data:", response); + } + } catch (error) { + console.error("Error fetching cross profile diagram data:", error); + response = null; + renderingFormat = null; + } finally { + isLoading = false; + } + } + function getDiagramRequestKey(datasetName, graphUri, packageUUID, filter) { return JSON.stringify({ datasetName, From 096adfad147f71a4b50174a40ff51109bd236854 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 10 Jun 2026 15:45:35 +0200 Subject: [PATCH 04/30] solved after merge problems --- .../rdfarchitect/database/DatabasePort.java | 16 ++ .../database/inmemory/GraphWithContext.java | 1 + .../inmemory/GraphWithContextCollection.java | 12 +- .../database/inmemory/InMemoryDatabase.java | 16 ++ .../inmemory/InMemoryDatabaseAdapter.java | 10 + .../inmemory/InMemoryDatabaseImpl.java | 13 ++ .../database/inmemory/SessionDataStore.java | 16 ++ .../inmemory/SessionDataStoreImpl.java | 26 +++ .../rdf/graph/wrapper/DiagramLayout.java | 3 +- .../services/diagrams/CrossProfileUtils.java | 38 ++++ .../diagrams/CustomDiagramService.java | 16 +- .../dl/select/QueryDiagramLayoutService.java | 16 +- .../services/select/QueryGraphService.java | 9 +- .../SvelteFlowClassContextMenu.svelte | 192 +++++++++--------- 14 files changed, 264 insertions(+), 120 deletions(-) create mode 100644 backend/src/main/java/org/rdfarchitect/services/diagrams/CrossProfileUtils.java diff --git a/backend/src/main/java/org/rdfarchitect/database/DatabasePort.java b/backend/src/main/java/org/rdfarchitect/database/DatabasePort.java index 8627d051..dedfe42e 100644 --- a/backend/src/main/java/org/rdfarchitect/database/DatabasePort.java +++ b/backend/src/main/java/org/rdfarchitect/database/DatabasePort.java @@ -60,6 +60,22 @@ public interface DatabasePort { */ UUID getCrossProfileDiagramUUID(String datasetName); + /** + * Returns the color of the given graph in the CrossProfileDiagram. + * + * @param graphIdentifier The identifier of the graph. + * @return The color as hex code of the graph. + */ + String getCrossProfileDiagramColor(GraphIdentifier graphIdentifier); + + /** + * Sets the color of the given graph in the CrossProfileDiagram. + * + * @param graphIdentifier The identifier of the graph. + * @param color The color as hex code. + */ + void setCrossProfileDiagramColor(GraphIdentifier graphIdentifier, String color); + /** * Loads the namespace prefix mapping for the dataset. * diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java index e69de29b..8b137891 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java @@ -0,0 +1 @@ + diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java index cc24d78d..6f5c490e 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContextCollection.java @@ -64,8 +64,8 @@ public class GraphWithContextCollection { @Getter private final UUID crossProfileDiagramUUID = UUID.randomUUID(); - // lock to prohibit dirty reads/writes - private final ReentrantLock lock = new ReentrantLock(); + private final ConcurrentMap crossProfileDiagramColors = + new ConcurrentHashMap<>(); // universal prefix map for all graphs private final PrefixMapping prefixes = new PrefixMappingImpl(); @@ -237,4 +237,12 @@ private void assertValidGraphName(String graphUri) { throw new IllegalArgumentException("Graph Uri " + graphUri + " is not a valid URI"); } } + + public String getCrossProfileDiagramColor(String graphUri) { + return crossProfileDiagramColors.getOrDefault(graphUri, null); + } + + public void setCrossProfileDiagramColor(String graphUri, String color) { + crossProfileDiagramColors.put(graphUri, color); + } } diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabase.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabase.java index c88153cd..dca0d4d4 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabase.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabase.java @@ -80,6 +80,22 @@ public interface InMemoryDatabase { */ UUID getCrossProfileDiagramUUID(String datasetName); + /** + * Returns the color of the given graph in the CrossProfileDiagram. + * + * @param graphIdentifier The identifier of the graph. + * @return The color as hex code of the graph. + */ + String getCrossProfileDiagramColor(GraphIdentifier graphIdentifier); + + /** + * Sets the color of the given graph in the CrossProfileDiagram. + * + * @param graphIdentifier The identifier of the graph. + * @param color The color as hex code. + */ + void setCrossProfileDiagramColor(GraphIdentifier graphIdentifier, String color); + /** * Creates a new named graph in a specified dataset. If the dataset does not exist yet, it will * be created. If the graph already exists, nothing happens. Merges the graph's prefix mapping diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseAdapter.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseAdapter.java index 8e1f0456..98281591 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseAdapter.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseAdapter.java @@ -57,6 +57,16 @@ public UUID getCrossProfileDiagramUUID(String datasetName) { return database.getCrossProfileDiagramUUID(datasetName); } + @Override + public String getCrossProfileDiagramColor(GraphIdentifier graphIdentifier) { + return database.getCrossProfileDiagramColor(graphIdentifier); + } + + @Override + public void setCrossProfileDiagramColor(GraphIdentifier graphIdentifier, String color) { + database.setCrossProfileDiagramColor(graphIdentifier, color); + } + @Override public PrefixMapping getPrefixMapping(String datasetName) { return database.getPrefixMapping(datasetName); diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseImpl.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseImpl.java index fbb17191..b1e247fc 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseImpl.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/InMemoryDatabaseImpl.java @@ -31,6 +31,7 @@ import org.rdfarchitect.database.GraphIdentifier; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; import org.rdfarchitect.rdf.graph.wrapper.DiagramLayout; +import org.rdfarchitect.services.diagrams.CrossProfileUtils; import java.util.List; import java.util.Map; @@ -75,6 +76,16 @@ public UUID getCrossProfileDiagramUUID(String datasetName) { return getOrCreateSessionDataStore().getCrossProfileDiagramUUID(datasetName); } + @Override + public String getCrossProfileDiagramColor(GraphIdentifier graphIdentifier) { + return getOrCreateSessionDataStore().getCrossProfileDiagramColor(graphIdentifier); + } + + @Override + public void setCrossProfileDiagramColor(GraphIdentifier graphIdentifier, String color) { + getOrCreateSessionDataStore().setCrossProfileDiagramColor(graphIdentifier, color); + } + @Override public GraphContext getGraphWithContext(GraphIdentifier graphIdentifier) { return getOrCreateSessionDataStore().getGraphWithContext(graphIdentifier); @@ -89,6 +100,8 @@ public void createGraph(GraphIdentifier graphIdentifier, Graph newGraph) { .setNsPrefixes(store.getPrefixMapping(graphIdentifier.datasetName())) .setNsPrefixes(newGraph.getPrefixMapping()); store.setPrefixMapping(graphIdentifier.datasetName(), currentPrefixMapping); + store.setCrossProfileDiagramColor( + graphIdentifier, CrossProfileUtils.generateRandomDarkColor()); } @Override diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStore.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStore.java index c0ed69a2..0bebe50c 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStore.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStore.java @@ -86,6 +86,22 @@ static Dataset wrapGraphInDataset(Graph graph, String graphUri) { */ UUID getCrossProfileDiagramUUID(String datasetName); + /** + * Returns the color of the given graph in the CrossProfileDiagram. + * + * @param graphIdentifier The identifier of the graph. + * @return The color as hex code of the graph. + */ + String getCrossProfileDiagramColor(GraphIdentifier graphIdentifier); + + /** + * Sets the color of the given graph in the CrossProfileDiagram. + * + * @param graphIdentifier The identifier of the graph. + * @param color The color as hex code. + */ + void setCrossProfileDiagramColor(GraphIdentifier graphIdentifier, String color); + /** * Creates a new named graph in a specified dataset. If the dataset does not exist yet, it will * be created. If the graph already exists, nothing happens. diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStoreImpl.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStoreImpl.java index 149aa14b..c10a3055 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStoreImpl.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/SessionDataStoreImpl.java @@ -131,6 +131,32 @@ public UUID getCrossProfileDiagramUUID(String datasetName) { } } + @Override + public String getCrossProfileDiagramColor(GraphIdentifier graphIdentifier) { + lock.lock(); + try { + assertThatDatasetExists(graphIdentifier.datasetName()); + return graphCollections + .get(graphIdentifier.datasetName()) + .getCrossProfileDiagramColor(graphIdentifier.graphUri()); + } finally { + lock.unlock(); + } + } + + @Override + public void setCrossProfileDiagramColor(GraphIdentifier graphIdentifier, String color) { + lock.lock(); + try { + assertThatDatasetExists(graphIdentifier.datasetName()); + graphCollections + .get(graphIdentifier.datasetName()) + .setCrossProfileDiagramColor(graphIdentifier.graphUri(), color); + } finally { + lock.unlock(); + } + } + @Override public void create(GraphIdentifier graphIdentifier, Graph newGraph) { lock.lock(); diff --git a/backend/src/main/java/org/rdfarchitect/rdf/graph/wrapper/DiagramLayout.java b/backend/src/main/java/org/rdfarchitect/rdf/graph/wrapper/DiagramLayout.java index af64c876..64feeef5 100644 --- a/backend/src/main/java/org/rdfarchitect/rdf/graph/wrapper/DiagramLayout.java +++ b/backend/src/main/java/org/rdfarchitect/rdf/graph/wrapper/DiagramLayout.java @@ -27,6 +27,7 @@ import java.util.UUID; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.Supplier; public class DiagramLayout { @@ -42,7 +43,7 @@ public DiagramLayout() { diagramLayoutModel.setNsPrefix("rdf", RDF.uri); } - public T read(java.util.function.Supplier action) { + public T read(Supplier action) { rwLock.readLock().lock(); try { return action.get(); diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/CrossProfileUtils.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/CrossProfileUtils.java new file mode 100644 index 00000000..9a543187 --- /dev/null +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/CrossProfileUtils.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.services.diagrams; + +import lombok.experimental.UtilityClass; + +import java.awt.Color; +import java.util.Random; + +@UtilityClass +public class CrossProfileUtils { + + public static String generateRandomDarkColor() { + Random random = new Random(); + + float hue = random.nextFloat(); + float saturation = 0.5f + random.nextFloat() * 0.5f; + float brightness = 0.3f + random.nextFloat() * 0.4f; + + Color color = Color.getHSBColor(hue, saturation, brightness); + return String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue()); + } +} diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java index 109b34ac..e3cec400 100644 --- a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java @@ -19,13 +19,13 @@ import lombok.RequiredArgsConstructor; +import org.apache.jena.query.ReadWrite; import org.rdfarchitect.api.dto.ClassUMLAdaptedDTO; import org.rdfarchitect.api.dto.crossProfileDiagram.ClassSourceDTO; import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramColorDataDTO; import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramDTO; import org.rdfarchitect.api.dto.crossProfileDiagram.GraphSourcedDTO; import org.rdfarchitect.api.dto.crossProfileDiagram.MergedClassDTO; -import org.apache.jena.query.ReadWrite; import org.rdfarchitect.database.DatabasePort; import org.rdfarchitect.database.GraphIdentifier; import org.rdfarchitect.database.inmemory.diagrams.CustomDiagram; @@ -54,10 +54,8 @@ public class CustomDiagramService implements GetCustomDiagramsUseCase, ReplaceCustomDiagramUseCase, DeleteCustomDiagramUseCase, - AddToDiagramUseCase, RemoveFromDiagramUseCase, CrossProfileColorUseCase { - RemoveFromDiagramUseCase { private final DatabasePort databasePort; private final GetClassListUseCase getClassListUseCase; @@ -86,8 +84,7 @@ public CrossProfileDiagramDTO getCrossProfileDiagram( for (var graphUri : graphUris) { var graphIdentifier = new GraphIdentifier(datasetName, graphUri); var classList = getClassListUseCase.getFullClassList(graphIdentifier); - var graphColor = - databasePort.getGraphWithContext(graphIdentifier).getCrossProfileDiagramColor(); + var graphColor = databasePort.getCrossProfileDiagramColor(graphIdentifier); for (var dto : classList) { var classUri = dto.getPrefix() + dto.getLabel(); @@ -194,7 +191,8 @@ private static void doDiagramLayout( crossProfileDiagramUUID, merged.getClassUri(), merged.getUuid()); - DiagramLayoutServiceUtils.insertDiagramObjectPoint(model, doMRID); + DiagramLayoutServiceUtils.insertDiagramObjectPoint( + model, crossProfileDiagramUUID, doMRID); } } }); @@ -293,8 +291,7 @@ public CrossProfileDiagramColorDataDTO getCrossProfileColors(String datasetName) var colorsDTO = new CrossProfileDiagramColorDataDTO(new HashMap<>()); for (var graphUri : graphUris) { var graphIdentifier = new GraphIdentifier(datasetName, graphUri); - var graphColor = - databasePort.getGraphWithContext(graphIdentifier).getCrossProfileDiagramColor(); + var graphColor = databasePort.getCrossProfileDiagramColor(graphIdentifier); colorsDTO.getGraphColors().put(graphUri, graphColor); } return colorsDTO; @@ -307,7 +304,8 @@ public void replaceCrossProfileColors(String datasetName, CrossProfileDiagramCol var graphIdentifier = new GraphIdentifier(datasetName, graphUri); var graphWithContext = databasePort.getGraphWithContext(graphIdentifier); if (dto.getGraphColors().containsKey(graphUri)) { - graphWithContext.setCrossProfileDiagramColor(dto.getGraphColors().get(graphUri)); + databasePort.setCrossProfileDiagramColor( + graphIdentifier, dto.getGraphColors().get(graphUri)); } } } diff --git a/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java b/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java index d1e450f4..b08277b5 100644 --- a/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java +++ b/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java @@ -42,10 +42,12 @@ public class QueryDiagramLayoutService implements FetchRenderingLayoutDataUseCas public RenderingLayoutData fetchRenderingLayoutData( GraphIdentifier graphIdentifier, UUID packageUUID) { try (var ctx = databasePort.getGraphWithContext(graphIdentifier).begin(ReadWrite.READ)) { - DiagramLayoutDelta diagramLayout = ctx.getDiagramLayout(); - var diagramLayoutModel = diagramLayout.getDiagramLayoutModel(); - return diagramLayout.read( - () -> fetchRenderingLayoutData(diagramLayout.getDefaultPackageMRID().getUuid(), diagramLayoutModel, packageUUID)); + DiagramLayoutDelta diagramLayoutDelta = ctx.getDiagramLayout(); + var diagramLayoutModel = diagramLayoutDelta.getDiagramLayoutModel(); + return fetchRenderingLayoutData( + diagramLayoutDelta.getDefaultPackageMRID().getUuid(), + diagramLayoutModel, + packageUUID); } } @@ -54,7 +56,11 @@ public RenderingLayoutData fetchGlobalRenderingLayoutData(String datasetName, UU var diagramLayout = databasePort.getDatasetDiagramLayout(datasetName); var diagramLayoutModel = diagramLayout.getDiagramLayoutModel(); return diagramLayout.read( - () -> fetchRenderingLayoutData(diagramLayout.getDefaultPackageMRID().getUuid(), diagramLayoutModel, diagramId)); + () -> + fetchRenderingLayoutData( + diagramLayout.getDefaultPackageMRID().getUuid(), + diagramLayoutModel, + diagramId)); } private RenderingLayoutData fetchRenderingLayoutData( diff --git a/backend/src/main/java/org/rdfarchitect/services/select/QueryGraphService.java b/backend/src/main/java/org/rdfarchitect/services/select/QueryGraphService.java index ef9a46e9..825c1edc 100644 --- a/backend/src/main/java/org/rdfarchitect/services/select/QueryGraphService.java +++ b/backend/src/main/java/org/rdfarchitect/services/select/QueryGraphService.java @@ -190,19 +190,16 @@ private List getReferencedClassList(GraphIdentifier graphIde @Override public List getFullClassList(GraphIdentifier graphIdentifier) { var prefixMapping = databasePort.getPrefixMapping(graphIdentifier.datasetName()); - var rdfGraph = databasePort.getGraphWithContext(graphIdentifier).getRdfGraph(); - var baseList = getClassList(graphIdentifier, false); - rdfGraph.begin(TxnType.READ); - try { + try (var ctx = databasePort.getGraphWithContext(graphIdentifier).begin(ReadWrite.READ)) { return baseList.stream() .filter(dto -> dto.getUuid() != null) .map( dto -> { var fullClass = CIMUMLObjectFactory.createCIMClassUMLAdapted( - rdfGraph, + ctx.getRdfGraph(), graphIdentifier.graphUri(), prefixMapping, dto.getUuid().toString()); @@ -210,8 +207,6 @@ public List getFullClassList(GraphIdentifier graphIdentifier return classMapper.toDTO(fullClass); }) .toList(); - } finally { - rdfGraph.end(); } } diff --git a/frontend/src/lib/rendering/svelteflow/components/SvelteFlowClassContextMenu.svelte b/frontend/src/lib/rendering/svelteflow/components/SvelteFlowClassContextMenu.svelte index d1bab39e..7ab0223c 100644 --- a/frontend/src/lib/rendering/svelteflow/components/SvelteFlowClassContextMenu.svelte +++ b/frontend/src/lib/rendering/svelteflow/components/SvelteFlowClassContextMenu.svelte @@ -174,108 +174,108 @@ {disabled} /> {#if editorState.selectedDiagram.getProperty("type") !== DiagramType.CROSS_PROFILE} - - - Copy - - - Extend Class - - - { - showSHACLDialog = true; - }} - faIcon={faDiagramProject} - > - Constraints - - - + - Move - - - { - e.preventDefault(); - handleMoveToTop(); - }} - faIcon={faAnglesUp} - disabled={classActionsDisabled || isAtFront} - > - Move to front - - { - e.preventDefault(); - handleMoveUp(); - }} - faIcon={faAngleUp} - disabled={classActionsDisabled || isAtFront} - > - Move up - - + + Extend Class + + + { + showSHACLDialog = true; + }} + faIcon={faDiagramProject} + > + Constraints + + + - Layer - + Move + + + { + e.preventDefault(); + handleMoveToTop(); + }} + faIcon={faAnglesUp} + disabled={classActionsDisabled || isAtFront} + > + Move to front + + { + e.preventDefault(); + handleMoveUp(); + }} + faIcon={faAngleUp} + disabled={classActionsDisabled || isAtFront} + > + Move up + + + Layer + + { + e.preventDefault(); + handleMoveDown(); + }} + faIcon={faAngleDown} + disabled={classActionsDisabled || isAtBack} + > + Move down + + { + e.preventDefault(); + handleMoveToBottom(); + }} + faIcon={faAnglesDown} + disabled={classActionsDisabled || isAtBack} + > + Move to bottom + + + + + {#if editorState.selectedDiagram.getProperty("type") !== DiagramType.PACKAGE} { - e.preventDefault(); - handleMoveDown(); - }} - faIcon={faAngleDown} - disabled={classActionsDisabled || isAtBack} + onSelect={openRemoveFromDiagramDialog} + faIcon={faMinus} + variant="danger" > - Move down + Remove from Diagram - { - e.preventDefault(); - handleMoveToBottom(); - }} - faIcon={faAnglesDown} - disabled={classActionsDisabled || isAtBack} - > - Move to bottom - - - - - {#if editorState.selectedDiagram.getProperty("type") !== DiagramType.PACKAGE} - - Remove from Diagram - - {/if} - - Delete class - - + {/if} + + Delete class + + {/if} From 1a6ec24f6c0e5177500bafffb52c8ddcdae10fac Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 10 Jun 2026 15:47:59 +0200 Subject: [PATCH 05/30] fixed license header --- .../database/inmemory/GraphWithContext.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java index 8b137891..01677357 100644 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java +++ b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java @@ -1 +1,18 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + From c4a1d5fec8eec288a5df146516ead1a93eaf4f1f Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 10 Jun 2026 15:53:15 +0200 Subject: [PATCH 06/30] deleted class --- .../database/inmemory/GraphWithContext.java | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java diff --git a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java b/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java deleted file mode 100644 index 01677357..00000000 --- a/backend/src/main/java/org/rdfarchitect/database/inmemory/GraphWithContext.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright (c) 2024-2026 SOPTIM AG - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - - From 05ae9e92005d1f2c6ed720570eab99fe2d44f67f Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 10 Jun 2026 16:59:38 +0200 Subject: [PATCH 07/30] added option to disable colored properties --- .../svelteflow/components/ClassNode.svelte | 21 +++++++++++++++---- frontend/src/routes/UserSettingDialog.svelte | 10 +++++++++ .../CrossProfileColorDialog.svelte | 7 ++++++- 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/frontend/src/lib/rendering/svelteflow/components/ClassNode.svelte b/frontend/src/lib/rendering/svelteflow/components/ClassNode.svelte index 0eba0a1b..f121423d 100644 --- a/frontend/src/lib/rendering/svelteflow/components/ClassNode.svelte +++ b/frontend/src/lib/rendering/svelteflow/components/ClassNode.svelte @@ -19,6 +19,7 @@ import { Handle, Position } from "@xyflow/svelte"; import { DiagramType, editorState } from "$lib/sharedState.svelte.js"; + import { userSettings } from "$lib/userSettings.svelte.js"; import { getPackageDisplayLabel } from "$lib/utils/package-label.js"; let { id, data, dragging } = $props(); @@ -116,7 +117,11 @@ {#each group.props as attr}
{attr.label}: {attr.type}  [{attr.multiplicity}]
@@ -126,7 +131,11 @@ {#each attributes as attr}
{attr.label}: {attr.type}  [{attr.multiplicity}]
@@ -141,7 +150,9 @@ {#each group.props as enumEntry}
@@ -153,7 +164,9 @@ {#each enumEntries as enumEntry}
diff --git a/frontend/src/routes/UserSettingDialog.svelte b/frontend/src/routes/UserSettingDialog.svelte index d7c22295..ecc34558 100644 --- a/frontend/src/routes/UserSettingDialog.svelte +++ b/frontend/src/routes/UserSettingDialog.svelte @@ -29,6 +29,7 @@ usePackagePrefix: false, defaultExportFormat: supportedRDFMediaTypes[0].mimeType, showPackagePrefix: false, + useColoredPropertiesInMergedView: true, normalizeComments: true, }; @@ -96,6 +97,15 @@ (localSettings["showPackagePrefix"] = false)} labelFirst={false} /> + + (localSettings["useColoredPropertiesInMergedView"] = true)} + callOnInputFalse={() => + (localSettings["useColoredPropertiesInMergedView"] = false)} + labelFirst={false} + /> Date: Thu, 11 Jun 2026 09:47:23 +0200 Subject: [PATCH 08/30] added color to association edges and fixed tests --- .../rendering/svelteflow/sub/EdgeDataDTO.java | 1 + .../models/cim/data/dto/CIMAssociation.java | 2 ++ .../RenderCIMCollectionSvelteFlowService.java | 1 + .../DiagramToCIMCollectionConverterService.java | 5 +++-- ...gramToCIMCollectionConverterServiceTest.java | 17 ++++++++++++++++- ...enderCIMCollectionSvelteFlowServiceTest.java | 5 +++-- .../components/AssociationEdge.svelte | 10 ++++++++-- .../svelteflow/components/EdgeMarkers.svelte | 4 ++-- 8 files changed, 36 insertions(+), 9 deletions(-) diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/EdgeDataDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/EdgeDataDTO.java index 43ed1950..d07d4389 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/EdgeDataDTO.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/rendering/svelteflow/sub/EdgeDataDTO.java @@ -30,4 +30,5 @@ public class EdgeDataDTO { private boolean useToAssociation; private boolean useFromAssociation; private String graphUri; + private String color; } diff --git a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMAssociation.java b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMAssociation.java index e71024a4..5835cd46 100644 --- a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMAssociation.java +++ b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMAssociation.java @@ -41,6 +41,8 @@ public class CIMAssociation { private String graphUri; + private String color; + private RDFSLabel label; private RDFSComment comment; diff --git a/backend/src/main/java/org/rdfarchitect/models/cim/rendering/svelteflow/RenderCIMCollectionSvelteFlowService.java b/backend/src/main/java/org/rdfarchitect/models/cim/rendering/svelteflow/RenderCIMCollectionSvelteFlowService.java index d93b6598..1e2f9be9 100644 --- a/backend/src/main/java/org/rdfarchitect/models/cim/rendering/svelteflow/RenderCIMCollectionSvelteFlowService.java +++ b/backend/src/main/java/org/rdfarchitect/models/cim/rendering/svelteflow/RenderCIMCollectionSvelteFlowService.java @@ -356,6 +356,7 @@ private EdgeDTO assembleAssociationEdgeDTO( .useToAssociation(useToAssociation) .useFromAssociation(useFromAssociation) .graphUri(from.getGraphUri()) + .color(from.getColor()) .build(); return EdgeDTO.builder() diff --git a/backend/src/main/java/org/rdfarchitect/services/rendering/DiagramToCIMCollectionConverterService.java b/backend/src/main/java/org/rdfarchitect/services/rendering/DiagramToCIMCollectionConverterService.java index 68150851..c2aec234 100644 --- a/backend/src/main/java/org/rdfarchitect/services/rendering/DiagramToCIMCollectionConverterService.java +++ b/backend/src/main/java/org/rdfarchitect/services/rendering/DiagramToCIMCollectionConverterService.java @@ -149,15 +149,16 @@ private void convertAssociationPairs(MergedClassDTO mergedClass, CIMCollection c for (var graphSourcedPair : mergedClass.getAssociationPairs()) { var pair = associationPairMapper.toCIMObject(graphSourcedPair.getValue()); var graphUri = graphSourcedPair.getGraphUri(); + var color = graphSourcedPair.getGraphColor(); if (pair.getFrom() != null) { collection .getAssociations() - .add(pair.getFrom().toBuilder().graphUri(graphUri).build()); + .add(pair.getFrom().toBuilder().graphUri(graphUri).color(color).build()); } if (pair.getTo() != null) { collection .getAssociations() - .add(pair.getTo().toBuilder().graphUri(graphUri).build()); + .add(pair.getTo().toBuilder().graphUri(graphUri).color(color).build()); } } } diff --git a/backend/src/test/java/org/rdfarchitect/cim/data/CIMCollectionConverter/DiagramToCIMCollectionConverterServiceTest.java b/backend/src/test/java/org/rdfarchitect/cim/data/CIMCollectionConverter/DiagramToCIMCollectionConverterServiceTest.java index 4f929ea4..a8fb0465 100644 --- a/backend/src/test/java/org/rdfarchitect/cim/data/CIMCollectionConverter/DiagramToCIMCollectionConverterServiceTest.java +++ b/backend/src/test/java/org/rdfarchitect/cim/data/CIMCollectionConverter/DiagramToCIMCollectionConverterServiceTest.java @@ -23,6 +23,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; +import org.rdfarchitect.api.dto.association.AssociationPairMapper; +import org.rdfarchitect.api.dto.attributes.AttributeMapper; +import org.rdfarchitect.api.dto.enumentries.EnumEntryMapper; import org.rdfarchitect.database.DatabasePort; import org.rdfarchitect.database.GraphContext; import org.rdfarchitect.database.GraphIdentifier; @@ -45,6 +48,9 @@ class DiagramToCIMCollectionConverterServiceTest { private DatabasePort databasePort; private GraphToCIMCollectionConverterService converter; + private AttributeMapper attributeMapper; + private EnumEntryMapper enumEntryMapper; + private AssociationPairMapper associationPairMapper; private DiagramToCIMCollectionConverterService service; private GraphIdentifier graphIdentifier; @@ -53,7 +59,16 @@ class DiagramToCIMCollectionConverterServiceTest { void setUp() { databasePort = mock(DatabasePort.class); converter = mock(GraphToCIMCollectionConverterService.class); - service = new DiagramToCIMCollectionConverterService(databasePort, converter); + attributeMapper = mock(AttributeMapper.class); + enumEntryMapper = mock(EnumEntryMapper.class); + associationPairMapper = mock(AssociationPairMapper.class); + service = + new DiagramToCIMCollectionConverterService( + databasePort, + converter, + attributeMapper, + enumEntryMapper, + associationPairMapper); graphIdentifier = new GraphIdentifier("dataset", "http://example.org#graph"); } diff --git a/backend/src/test/java/org/rdfarchitect/services/rendering/RenderCIMCollectionSvelteFlowServiceTest.java b/backend/src/test/java/org/rdfarchitect/services/rendering/RenderCIMCollectionSvelteFlowServiceTest.java index b443383e..b3a1a218 100644 --- a/backend/src/test/java/org/rdfarchitect/services/rendering/RenderCIMCollectionSvelteFlowServiceTest.java +++ b/backend/src/test/java/org/rdfarchitect/services/rendering/RenderCIMCollectionSvelteFlowServiceTest.java @@ -22,6 +22,7 @@ import org.apache.jena.datatypes.xsd.XSDDatatype; import org.junit.jupiter.api.Test; import org.rdfarchitect.api.dto.rendering.svelteflow.SvelteFlowDTO; +import org.rdfarchitect.api.dto.rendering.svelteflow.sub.EnumEntryDTO; import org.rdfarchitect.models.cim.data.dto.relations.CIMSBelongsToCategory; import org.rdfarchitect.models.cim.data.dto.relations.CIMSMultiplicity; import org.rdfarchitect.models.cim.data.dto.relations.CIMSStereotype; @@ -136,8 +137,8 @@ void renderUML_singleEnum_createsNodeWithCorrectData() { .contains("abstract"); assertThat(nodeDTO.getData().getEnumEntries()) .hasSize(2) - .contains("enumEntry1") - .contains("enumEntry2"); + .extracting(EnumEntryDTO::getLabel) + .contains("enumEntry1", "enumEntry2"); } @Test diff --git a/frontend/src/lib/rendering/svelteflow/components/AssociationEdge.svelte b/frontend/src/lib/rendering/svelteflow/components/AssociationEdge.svelte index c3f93091..e68c869c 100644 --- a/frontend/src/lib/rendering/svelteflow/components/AssociationEdge.svelte +++ b/frontend/src/lib/rendering/svelteflow/components/AssociationEdge.svelte @@ -23,16 +23,22 @@ useInternalNode, } from "@xyflow/svelte"; + import { userSettings } from "$lib/userSettings.svelte.js"; + import { getEdgeParams } from "./edgeUtils.ts"; let { id, source, target, data } = $props(); - - const style = "stroke-width: 2px; stroke: #000"; let markerEnd = data.useToAssociation ? "url(#associationTo)" : ""; let markerStart = data.useFromAssociation ? "url(#associationFrom)" : ""; let sourceNode = useInternalNode(source); let targetNode = useInternalNode(target); + let style = $derived( + userSettings.get("useColoredPropertiesInMergedView") && data.color + ? `stroke-width: 2px; stroke: ${data.color};` + : "stroke-width: 2px; stroke: #000;", + ); + let edgeParams = $derived.by(() => { if (sourceNode.current && targetNode.current) { return getEdgeParams(sourceNode.current, targetNode.current); diff --git a/frontend/src/lib/rendering/svelteflow/components/EdgeMarkers.svelte b/frontend/src/lib/rendering/svelteflow/components/EdgeMarkers.svelte index 96e144d8..bd33f4f5 100644 --- a/frontend/src/lib/rendering/svelteflow/components/EdgeMarkers.svelte +++ b/frontend/src/lib/rendering/svelteflow/components/EdgeMarkers.svelte @@ -41,7 +41,7 @@ markerHeight="10" orient="auto" > - + - + From 2d59a0ca79ed50206ba718b651110a40d22e5266 Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 11 Jun 2026 10:01:15 +0200 Subject: [PATCH 09/30] fixes --- .../api/dto/crossProfileDiagram/ClassSourceDTO.java | 2 +- .../rdfarchitect/services/diagrams/CustomDiagramService.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java index d86d5622..ccf76e23 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java @@ -25,6 +25,6 @@ @Data @AllArgsConstructor public class ClassSourceDTO { - private UUID classUuid; + private UUID uuid; private String graphUri; } diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java index e3cec400..029036b8 100644 --- a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java @@ -302,7 +302,6 @@ public void replaceCrossProfileColors(String datasetName, CrossProfileDiagramCol var graphUris = databasePort.listGraphUris(datasetName); for (var graphUri : graphUris) { var graphIdentifier = new GraphIdentifier(datasetName, graphUri); - var graphWithContext = databasePort.getGraphWithContext(graphIdentifier); if (dto.getGraphColors().containsKey(graphUri)) { databasePort.setCrossProfileDiagramColor( graphIdentifier, dto.getGraphColors().get(graphUri)); From 53a21f7823ed6424385c0da2d0ede3c627ccd457 Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 11 Jun 2026 10:03:46 +0200 Subject: [PATCH 10/30] fixes --- .../org/rdfarchitect/services/diagrams/CrossProfileUtils.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/CrossProfileUtils.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/CrossProfileUtils.java index 9a543187..d85a5f04 100644 --- a/backend/src/main/java/org/rdfarchitect/services/diagrams/CrossProfileUtils.java +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/CrossProfileUtils.java @@ -25,9 +25,9 @@ @UtilityClass public class CrossProfileUtils { - public static String generateRandomDarkColor() { - Random random = new Random(); + private static final Random random = new Random(); + public static String generateRandomDarkColor() { float hue = random.nextFloat(); float saturation = 0.5f + random.nextFloat() * 0.5f; float brightness = 0.3f + random.nextFloat() * 0.4f; From b93d0a213743c244ffb0b2a0ecd01f1c21fe1ddb Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 11 Jun 2026 11:34:40 +0200 Subject: [PATCH 11/30] ignore not needed fields --- .../java/org/rdfarchitect/api/dto/ClassUMLAdaptedMapper.java | 1 + .../org/rdfarchitect/api/dto/association/AssociationMapper.java | 2 ++ .../org/rdfarchitect/api/dto/attributes/AttributeMapper.java | 2 ++ .../org/rdfarchitect/api/dto/enumentries/EnumEntryMapper.java | 2 ++ 4 files changed, 7 insertions(+) diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/ClassUMLAdaptedMapper.java b/backend/src/main/java/org/rdfarchitect/api/dto/ClassUMLAdaptedMapper.java index 0bcca5c6..8bb1e252 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/ClassUMLAdaptedMapper.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/ClassUMLAdaptedMapper.java @@ -54,6 +54,7 @@ public interface ClassUMLAdaptedMapper { List toDTOList(List cimClassList); @Mapping(target = "uri", source = ".") + @Mapping(target = "graphUri", ignore = true) CIMClassUMLAdapted toCIMObject(ClassUMLAdaptedDTO dto); List toCIMObjectList(List dtoList); diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/association/AssociationMapper.java b/backend/src/main/java/org/rdfarchitect/api/dto/association/AssociationMapper.java index 449cb1af..57971002 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/association/AssociationMapper.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/association/AssociationMapper.java @@ -48,6 +48,8 @@ public interface AssociationMapper { @Mapping(target = "uri", source = "dto") @Mapping(target = "domain", source = "dto") @Mapping(target = "inverseRoleName", expression = "java(buildInverseRoleName(inverseUri))") + @Mapping(target = "graphUri", ignore = true) + @Mapping(target = "color", ignore = true) CIMAssociation toCIMObject(AssociationDTO dto, String inverseUri); default String toDomain(URI value) { diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/attributes/AttributeMapper.java b/backend/src/main/java/org/rdfarchitect/api/dto/attributes/AttributeMapper.java index f1428f96..5bda17de 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/attributes/AttributeMapper.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/attributes/AttributeMapper.java @@ -67,6 +67,8 @@ default String map(URI value) { "java(new CIMSStereotype(\"http://iec.ch/TC57/NonStandard/UML#attribute\"))") @Mapping(target = "fixedValue", source = ".") @Mapping(target = "defaultValue", source = ".") + @Mapping(target = "graphUri", ignore = true) + @Mapping(target = "color", ignore = true) CIMAttribute toCIMObject(AttributeDTO dto); List toCIMObjectList(List dtoList); diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/enumentries/EnumEntryMapper.java b/backend/src/main/java/org/rdfarchitect/api/dto/enumentries/EnumEntryMapper.java index 88690781..2eaa1dc0 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/enumentries/EnumEntryMapper.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/enumentries/EnumEntryMapper.java @@ -47,6 +47,8 @@ public interface EnumEntryMapper { @Mapping(target = "uri", source = ".") @Mapping(target = "type", source = ".") + @Mapping(target = "graphUri", ignore = true) + @Mapping(target = "color", ignore = true) CIMEnumEntry toCIMObject(EnumEntryDTO dto); List toCIMObjectList(List dtoList); From 29068ac6597f7cc421b423c673ef9c4a93fa9baa Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 11 Jun 2026 11:53:36 +0200 Subject: [PATCH 12/30] fix --- .../api/dto/crossProfileDiagram/ClassSourceDTO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java index ccf76e23..d86d5622 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java @@ -25,6 +25,6 @@ @Data @AllArgsConstructor public class ClassSourceDTO { - private UUID uuid; + private UUID classUuid; private String graphUri; } From 498f6bc121e0bbb07bbf6c2c0633e0ce252aa01e Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 11 Jun 2026 12:00:03 +0200 Subject: [PATCH 13/30] fix --- .../api/dto/crossProfileDiagram/ClassSourceDTO.java | 2 +- .../src/routes/mainpage/classEditor/mergedClassEditor.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java index d86d5622..1315ac7d 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/ClassSourceDTO.java @@ -25,6 +25,6 @@ @Data @AllArgsConstructor public class ClassSourceDTO { - private UUID classUuid; + private UUID classUUID; private String graphUri; } diff --git a/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte b/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte index 0770bf07..68cb35c6 100644 --- a/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte +++ b/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte @@ -91,7 +91,7 @@
{/key} From 7660c9b3eaecbf6d070ad3b8a1cacf224c24f16c Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 11 Jun 2026 12:23:05 +0200 Subject: [PATCH 14/30] fix --- .../rdf/graph/wrapper/DiagramLayout.java | 22 -------- .../diagrams/CustomDiagramService.java | 52 +++++++++---------- .../dl/select/QueryDiagramLayoutService.java | 8 +-- 3 files changed, 26 insertions(+), 56 deletions(-) diff --git a/backend/src/main/java/org/rdfarchitect/rdf/graph/wrapper/DiagramLayout.java b/backend/src/main/java/org/rdfarchitect/rdf/graph/wrapper/DiagramLayout.java index 64feeef5..c3d47b9c 100644 --- a/backend/src/main/java/org/rdfarchitect/rdf/graph/wrapper/DiagramLayout.java +++ b/backend/src/main/java/org/rdfarchitect/rdf/graph/wrapper/DiagramLayout.java @@ -26,38 +26,16 @@ import org.rdfarchitect.dl.rdf.resources.CIM; import java.util.UUID; -import java.util.concurrent.locks.ReentrantReadWriteLock; -import java.util.function.Supplier; public class DiagramLayout { @Getter private final Model diagramLayoutModel; @Getter private final MRID defaultPackageMRID; - private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); - public DiagramLayout() { defaultPackageMRID = new MRID(UUID.randomUUID()); diagramLayoutModel = ModelFactory.createDefaultModel(); diagramLayoutModel.setNsPrefix(CIM.PREFIX, CIM.NAMESPACE); diagramLayoutModel.setNsPrefix("rdf", RDF.uri); } - - public T read(Supplier action) { - rwLock.readLock().lock(); - try { - return action.get(); - } finally { - rwLock.readLock().unlock(); - } - } - - public void write(Runnable action) { - rwLock.writeLock().lock(); - try { - action.run(); - } finally { - rwLock.writeLock().unlock(); - } - } } diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java index 029036b8..17c50fa1 100644 --- a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java @@ -167,35 +167,31 @@ private static void doDiagramLayout( DiagramLayout diagramLayout, UUID crossProfileDiagramUUID, Map mergeMap) { - diagramLayout.write( - () -> { - var model = diagramLayout.getDiagramLayoutModel(); - if (DLObjectFetcher.fetchDiagram(model, crossProfileDiagramUUID) == null) { - DiagramLayoutServiceUtils.insertDiagram( - model, crossProfileDiagramUUID, "CrossProfileDiagram"); - } - var existingDOs = - DLObjectFetcher.fetchDiagramDOs( - model, new MRID(crossProfileDiagramUUID)); - var existingClassUUIDs = - existingDOs.stream() - .map(DiagramObject::getBelongsToIdentifiedObject) - .map(MRID::getUuid) - .collect(Collectors.toSet()); - for (var merged : mergeMap.values()) { - if (!existingClassUUIDs.contains(merged.getUuid())) { - var doMRID = - DiagramLayoutServiceUtils.insertDiagramObject( - model, - crossProfileDiagramUUID, - merged.getClassUri(), - merged.getUuid()); - DiagramLayoutServiceUtils.insertDiagramObjectPoint( - model, crossProfileDiagramUUID, doMRID); - } - } - }); + var model = diagramLayout.getDiagramLayoutModel(); + if (DLObjectFetcher.fetchDiagram(model, crossProfileDiagramUUID) == null) { + DiagramLayoutServiceUtils.insertDiagram( + model, crossProfileDiagramUUID, "CrossProfileDiagram"); + } + var existingDOs = DLObjectFetcher.fetchDiagramDOs(model, new MRID(crossProfileDiagramUUID)); + var existingClassUUIDs = + existingDOs.stream() + .map(DiagramObject::getBelongsToIdentifiedObject) + .map(MRID::getUuid) + .collect(Collectors.toSet()); + + for (var merged : mergeMap.values()) { + if (!existingClassUUIDs.contains(merged.getUuid())) { + var doMRID = + DiagramLayoutServiceUtils.insertDiagramObject( + model, + crossProfileDiagramUUID, + merged.getClassUri(), + merged.getUuid()); + DiagramLayoutServiceUtils.insertDiagramObjectPoint( + model, crossProfileDiagramUUID, doMRID); + } + } } @Override diff --git a/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java b/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java index b08277b5..6a0e3549 100644 --- a/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java +++ b/backend/src/main/java/org/rdfarchitect/services/dl/select/QueryDiagramLayoutService.java @@ -55,12 +55,8 @@ public RenderingLayoutData fetchRenderingLayoutData( public RenderingLayoutData fetchGlobalRenderingLayoutData(String datasetName, UUID diagramId) { var diagramLayout = databasePort.getDatasetDiagramLayout(datasetName); var diagramLayoutModel = diagramLayout.getDiagramLayoutModel(); - return diagramLayout.read( - () -> - fetchRenderingLayoutData( - diagramLayout.getDefaultPackageMRID().getUuid(), - diagramLayoutModel, - diagramId)); + return fetchRenderingLayoutData( + diagramLayout.getDefaultPackageMRID().getUuid(), diagramLayoutModel, diagramId); } private RenderingLayoutData fetchRenderingLayoutData( From 0a8d95d54904701eb9a2586ba8ce019cced1260a Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 11 Jun 2026 12:47:54 +0200 Subject: [PATCH 15/30] fix --- .../GraphToCIMCollectionConverterService.java | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/backend/src/main/java/org/rdfarchitect/services/rendering/GraphToCIMCollectionConverterService.java b/backend/src/main/java/org/rdfarchitect/services/rendering/GraphToCIMCollectionConverterService.java index fb30bc6f..2d350a27 100644 --- a/backend/src/main/java/org/rdfarchitect/services/rendering/GraphToCIMCollectionConverterService.java +++ b/backend/src/main/java/org/rdfarchitect/services/rendering/GraphToCIMCollectionConverterService.java @@ -162,12 +162,10 @@ private void fetchClasses( CIMCollection cimCollection) { if (filter.getAllowedUUIDs() != null && !filter.getAllowedUUIDs().isEmpty()) { fetchSpecifiedClasses(graph, graphIdentifier, filter, cimCollection); - } else if (filter.getPackageUUID() != null && !filter.getPackageUUID().equals("default")) { + } else { fetchClassesInPackage(graph, graphIdentifier, filter, cimCollection); fetchExternalAssociatedClasses(graph, graphIdentifier, filter, cimCollection); fetchExternallyInheritanceRelatedClasses(graph, graphIdentifier, filter, cimCollection); - } else { - fetchAllClasses(graph, graphIdentifier, filter, cimCollection); } clearSuperClassRelations(filter, cimCollection); @@ -175,18 +173,6 @@ private void fetchClasses( clearInheritanceToNonExistingClasses(cimCollection); } - private void fetchAllClasses( - Graph graph, - GraphIdentifier graphIdentifier, - GraphFilter filter, - CIMCollection cimCollection) { - var classQueryBuilder = - CIMQueries.getClassesQuery( - databasePort.getPrefixMapping(graphIdentifier.datasetName()), - graphIdentifier.graphUri()); - fetchClasses(graph, graphIdentifier, filter, cimCollection, classQueryBuilder); - } - private void clearSuperClassRelations(GraphFilter filter, CIMCollection cimCollection) { if (filter.isIncludeInheritance()) { return; From 9b6babb3119a711cb524d6b0ac1424f67ab77920 Mon Sep 17 00:00:00 2001 From: Philipp Kirchner Date: Thu, 11 Jun 2026 13:01:21 +0200 Subject: [PATCH 16/30] fixed Missing Override annotation Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> Signed-off-by: Philipp Kirchner --- .../org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java index 23b62a48..224c6b18 100644 --- a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java +++ b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java @@ -28,7 +28,7 @@ @EqualsAndHashCode(callSuper = true) @Data -@SuperBuilder(toBuilder = true) +@SuperBuilder(toBuilder = true, toBuilderMethodName = "toBuilder", toBuilderMethodAnnotations = @Override) @NoArgsConstructor public class CIMMergedClass extends CIMClass { From fd48b2d2ccdf48d6870aa7cdb7ac835d1452d128 Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 11 Jun 2026 13:03:56 +0200 Subject: [PATCH 17/30] format --- .../org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java index 224c6b18..c78ea451 100644 --- a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java +++ b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java @@ -28,7 +28,10 @@ @EqualsAndHashCode(callSuper = true) @Data -@SuperBuilder(toBuilder = true, toBuilderMethodName = "toBuilder", toBuilderMethodAnnotations = @Override) +@SuperBuilder( + toBuilder = true, + toBuilderMethodName = "toBuilder", + toBuilderMethodAnnotations = @Override) @NoArgsConstructor public class CIMMergedClass extends CIMClass { From 7b30326914d1c109e96b69486884ae92b20dc58a Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 11 Jun 2026 13:07:33 +0200 Subject: [PATCH 18/30] Revert "format" This reverts commit fd48b2d2ccdf48d6870aa7cdb7ac835d1452d128. --- .../org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java index c78ea451..224c6b18 100644 --- a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java +++ b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java @@ -28,10 +28,7 @@ @EqualsAndHashCode(callSuper = true) @Data -@SuperBuilder( - toBuilder = true, - toBuilderMethodName = "toBuilder", - toBuilderMethodAnnotations = @Override) +@SuperBuilder(toBuilder = true, toBuilderMethodName = "toBuilder", toBuilderMethodAnnotations = @Override) @NoArgsConstructor public class CIMMergedClass extends CIMClass { From f892cab095f20646a8cb0811ad54efd96d95a385 Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 11 Jun 2026 13:07:36 +0200 Subject: [PATCH 19/30] Revert "fixed Missing Override annotation" This reverts commit 9b6babb3119a711cb524d6b0ac1424f67ab77920. --- .../org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java index 224c6b18..23b62a48 100644 --- a/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java +++ b/backend/src/main/java/org/rdfarchitect/models/cim/data/dto/CIMMergedClass.java @@ -28,7 +28,7 @@ @EqualsAndHashCode(callSuper = true) @Data -@SuperBuilder(toBuilder = true, toBuilderMethodName = "toBuilder", toBuilderMethodAnnotations = @Override) +@SuperBuilder(toBuilder = true) @NoArgsConstructor public class CIMMergedClass extends CIMClass { From 82ba9a096f8a9a3fc8c43ae0237419fafba5844d Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 11 Jun 2026 13:50:36 +0200 Subject: [PATCH 20/30] improved loading time of the merged class editor --- .../services/diagrams/CustomDiagramService.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java index 17c50fa1..3fd822ad 100644 --- a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java @@ -83,7 +83,12 @@ public CrossProfileDiagramDTO getCrossProfileDiagram( for (var graphUri : graphUris) { var graphIdentifier = new GraphIdentifier(datasetName, graphUri); - var classList = getClassListUseCase.getFullClassList(graphIdentifier); + List classList; + if (includeProperties) { + classList = getClassListUseCase.getFullClassList(graphIdentifier); + } else { + classList = getClassListUseCase.getClassList(graphIdentifier, false); + } var graphColor = databasePort.getCrossProfileDiagramColor(graphIdentifier); for (var dto : classList) { From 38f66642a27df26b48ee21ee8c7a6620176804a3 Mon Sep 17 00:00:00 2001 From: Philipp Date: Fri, 12 Jun 2026 10:33:14 +0200 Subject: [PATCH 21/30] added show in diagram to merged classes --- .../crossProfileDiagram/MergedClassDTO.java | 1 + .../diagrams/CustomDiagramService.java | 1 + .../packageNavigation/ClassEntry.svelte | 112 ++++++++++-------- .../CrossProfileDiagramsSection.svelte | 74 ++++++++++-- .../routes/mainpage/renderingWrapper.svelte | 2 - 5 files changed, 123 insertions(+), 67 deletions(-) diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java index a9c34507..c39821ce 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/crossProfileDiagram/MergedClassDTO.java @@ -35,6 +35,7 @@ public class MergedClassDTO { private UUID uuid; private String classUri; + private String label; private List sources; private List> superClasses; private List> attributes; diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java index 3fd822ad..164a4929 100644 --- a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java @@ -102,6 +102,7 @@ public CrossProfileDiagramDTO getCrossProfileDiagram( new MergedClassDTO( mergedUuid, uri, + dto.getLabel(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), diff --git a/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte b/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte index 8493af8b..08ed185d 100644 --- a/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte @@ -55,6 +55,8 @@ namespaces = [], readonly = false, onPackChange = () => {}, + classType = ClassType.NORMAL_CLASS, + diagramType = DiagramType.PACKAGE, } = $props(); let showDeleteDependenciesDialog = $state(false); @@ -84,15 +86,16 @@ eventStack.executeNewestEvent(classNavEntry.id); editorState.selectedClassDataset.updateValue(datasetNavEntry.id); editorState.selectedClassGraph.updateValue(graphNavEntry.id); - editorState.selectedClassType.updateValue(ClassType.NORMAL_CLASS); + editorState.selectedClassType.updateValue(classType); editorState.selectedClassUUID.updateValue(classNavEntry.id); return; } //The event executed to open the discard confirm delete dialog eventStack.executeNewestEvent({ datasetName: datasetNavEntry.id, - graphUri: graphNavEntry.id, + graphUri: graphNavEntry?.id ?? null, classUuid: classNavEntry.id, + classType: classType, }); } @@ -107,8 +110,9 @@ function showClassInPackage() { editorState.selectedDataset.updateValue(datasetNavEntry.id); editorState.selectedGraph.updateValue(graphNavEntry.id); + editorState.selectedClassType.updateValue(classType); editorState.selectedDiagram.updateValue({ - type: DiagramType.PACKAGE, + type: diagramType, id: classNavEntry.parent?.id ?? "default", }); selectClass(); @@ -139,78 +143,82 @@ /> - - Copy - - + {#if classType === ClassType.NORMAL_CLASS} + + Copy + + + {/if} Show in diagram - { - showSHACLDialog = true; - }} - faIcon={faDiagramProject} - > - Constraints - - - { - showExtendClassDialog = true; - }} - faIcon={faFileExport} - > - Extend Class - - {#if !diagramId} + {#if classType === ClassType.NORMAL_CLASS} { - showAddToGraphDiagramDialog = true; + showSHACLDialog = true; }} - faIcon={faObjectGroup} + faIcon={faDiagramProject} > - Add to Profile Diagram + Constraints + { - showAddToDatasetDiagramDialog = true; + showExtendClassDialog = true; }} - faIcon={faObjectGroup} + faIcon={faFileExport} > - Add to Dataset Diagram + Extend Class - {/if} - - {#if diagramId} + {#if !diagramId} + { + showAddToGraphDiagramDialog = true; + }} + faIcon={faObjectGroup} + > + Add to Profile Diagram + + { + showAddToDatasetDiagramDialog = true; + }} + faIcon={faObjectGroup} + > + Add to Dataset Diagram + + {/if} + + {#if diagramId} + { + showRemoveFromDiagramDialog = true; + }} + faIcon={faMinus} + variant="danger" + > + Remove from Diagram + + {/if} { - showRemoveFromDiagramDialog = true; + selectClass(); + showDeleteDependenciesDialog = true; }} - faIcon={faMinus} + disabled={readonly} + faIcon={faTrash} variant="danger" > - Remove from Diagram + Delete Class {/if} - { - selectClass(); - showDeleteDependenciesDialog = true; - }} - disabled={readonly} - faIcon={faTrash} - variant="danger" - > - Delete Class - diff --git a/frontend/src/routes/mainpage/packageNavigation/CrossProfileDiagramsSection.svelte b/frontend/src/routes/mainpage/packageNavigation/CrossProfileDiagramsSection.svelte index b557c853..8c3a8c7b 100644 --- a/frontend/src/routes/mainpage/packageNavigation/CrossProfileDiagramsSection.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/CrossProfileDiagramsSection.svelte @@ -21,15 +21,46 @@ faFileExport, } from "@fortawesome/free-solid-svg-icons"; + import { getCrossProfileDiagram } from "$lib/api/apiDatasetUtils.js"; import { ContextMenu } from "$lib/components/bitsui/contextmenu"; import NavigationEntry from "$lib/components/navigation/NavigationEntry.svelte"; - import { DiagramType, editorState } from "$lib/sharedState.svelte.js"; + import { + ClassType, + DiagramType, + editorState, + } from "$lib/sharedState.svelte.js"; + import ClassEntry from "./ClassEntry.svelte"; import CrossProfileColorDialog from "./custom-diagram-dialogs/CrossProfileColorDialog.svelte"; let { datasetNavEntry } = $props(); let showColorDialog = $state(false); + let isOpen = $state(false); + let classes = $state([]); + + const isMergedViewSelected = $derived( + !editorState.selectedGraph.getValue() && + editorState.selectedDataset.getValue() === datasetNavEntry.label && + editorState.selectedDiagram.getProperty("type") === + DiagramType.CROSS_PROFILE, + ); + + $effect(() => { + if (!datasetNavEntry.label) return; + getCrossProfileDiagram(datasetNavEntry.label).then(diagram => { + classes = diagram?.classes ?? []; + }); + }); + + function selectMergedView() { + editorState.selectedDataset.updateValue(datasetNavEntry.label); + editorState.selectedGraph.updateValue(null); + editorState.selectedDiagram.updateValue({ + type: DiagramType.CROSS_PROFILE, + id: datasetNavEntry.crossProfileID, + }); + }
0} + expanded={isOpen} + isSelected={isMergedViewSelected} onclick={() => { - editorState.selectedDataset.updateValue(datasetNavEntry.label); - editorState.selectedGraph.updateValue(null); - editorState.selectedDiagram.updateValue({ - type: DiagramType.CROSS_PROFILE, - id: datasetNavEntry.crossProfileID, - }); + selectMergedView(); + isOpen = !isOpen; }} + onToggle={() => (isOpen = !isOpen)} /> @@ -72,6 +97,29 @@ +{#if isOpen && classes.length > 0} +
+ {#each classes as cls (cls.uuid)} + selectMergedView(), + }, + }} + classType={ClassType.MERGED_CLASS} + diagramType={DiagramType.CROSS_PROFILE} + readonly={true} + /> + {/each} +
+{/if} + Date: Fri, 12 Jun 2026 10:39:31 +0200 Subject: [PATCH 22/30] refactoring --- .../diagrams/CustomDiagramService.java | 70 +++++++++++-------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java index 164a4929..f5f05645 100644 --- a/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java +++ b/backend/src/main/java/org/rdfarchitect/services/diagrams/CustomDiagramService.java @@ -127,48 +127,60 @@ public CrossProfileDiagramDTO getCrossProfileDiagram( private static void mergeProperties( String graphUri, ClassUMLAdaptedDTO dto, MergedClassDTO merged, String graphColor) { if (dto.getAttributes() != null) { - dto.getAttributes() - .forEach( - attr -> - merged.getAttributes() - .add( - new GraphSourcedDTO<>( - graphUri, graphColor, attr))); + mergeAttributes(graphUri, dto, merged, graphColor); } if (dto.getEnumEntries() != null) { - dto.getEnumEntries() - .forEach( - entry -> - merged.getEnumEntries() - .add( - new GraphSourcedDTO<>( - graphUri, graphColor, entry))); + mergeEnumEntries(graphUri, dto, merged, graphColor); } if (dto.getAssociationPairs() != null) { - dto.getAssociationPairs() - .forEach( - assoc -> - merged.getAssociationPairs() - .add( - new GraphSourcedDTO<>( - graphUri, graphColor, assoc))); + mergeAssociationPairs(graphUri, dto, merged, graphColor); } if (dto.getSuperClass() != null) { merged.getSuperClasses() .add(new GraphSourcedDTO<>(graphUri, graphColor, dto.getSuperClass())); } if (dto.getStereotypes() != null) { - dto.getStereotypes() - .forEach( - stereotype -> { - if (!merged.getStereotypes() - .contains(new CIMSStereotype(stereotype))) { - merged.getStereotypes().add(new CIMSStereotype(stereotype)); - } - }); + mergeStereotypes(dto, merged); } } + private static void mergeAttributes( + String graphUri, ClassUMLAdaptedDTO dto, MergedClassDTO merged, String graphColor) { + dto.getAttributes() + .forEach( + attr -> + merged.getAttributes() + .add(new GraphSourcedDTO<>(graphUri, graphColor, attr))); + } + + private static void mergeEnumEntries( + String graphUri, ClassUMLAdaptedDTO dto, MergedClassDTO merged, String graphColor) { + dto.getEnumEntries() + .forEach( + entry -> + merged.getEnumEntries() + .add(new GraphSourcedDTO<>(graphUri, graphColor, entry))); + } + + private static void mergeAssociationPairs( + String graphUri, ClassUMLAdaptedDTO dto, MergedClassDTO merged, String graphColor) { + dto.getAssociationPairs() + .forEach( + assoc -> + merged.getAssociationPairs() + .add(new GraphSourcedDTO<>(graphUri, graphColor, assoc))); + } + + private static void mergeStereotypes(ClassUMLAdaptedDTO dto, MergedClassDTO merged) { + dto.getStereotypes() + .forEach( + stereotype -> { + if (!merged.getStereotypes().contains(new CIMSStereotype(stereotype))) { + merged.getStereotypes().add(new CIMSStereotype(stereotype)); + } + }); + } + private static void doDiagramLayout( DiagramLayout diagramLayout, UUID crossProfileDiagramUUID, From 110ee82583df77e70c4ee39c615295c9b4f2e246 Mon Sep 17 00:00:00 2001 From: Philipp Date: Fri, 12 Jun 2026 10:49:24 +0200 Subject: [PATCH 23/30] added tests --- ...ProfileDiagramColorRestControllerTest.java | 61 +++++++++++++++ .../CrossProfileDiagramIDControllerTest.java | 51 ++++++++++++ ...ileDiagramRenderingRestControllerTest.java | 78 +++++++++++++++++++ ...CrossProfileDiagramRestControllerTest.java | 54 +++++++++++++ 4 files changed, 244 insertions(+) create mode 100644 backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramColorRestControllerTest.java create mode 100644 backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramIDControllerTest.java create mode 100644 backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRenderingRestControllerTest.java create mode 100644 backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestControllerTest.java diff --git a/backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramColorRestControllerTest.java b/backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramColorRestControllerTest.java new file mode 100644 index 00000000..395ca156 --- /dev/null +++ b/backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramColorRestControllerTest.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.api.controller.datasets.diagrams; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramColorDataDTO; +import org.rdfarchitect.services.diagrams.CrossProfileColorUseCase; +import org.springframework.http.HttpHeaders; + +import java.util.Map; + +class CrossProfileDiagramColorRestControllerTest { + + private CrossProfileColorUseCase colorUseCase; + private CrossProfileDiagramColorRestController controller; + + @BeforeEach + void setUp() { + colorUseCase = mock(CrossProfileColorUseCase.class); + controller = new CrossProfileDiagramColorRestController(colorUseCase); + } + + @Test + void getCrossProfileColors_returnsDTOFromUseCase() { + var expectedDTO = new CrossProfileDiagramColorDataDTO(Map.of("graph-a", "#ff0000")); + when(colorUseCase.getCrossProfileColors("my-dataset")).thenReturn(expectedDTO); + + var result = controller.getCrossProfileColors(HttpHeaders.ORIGIN, "my-dataset"); + + assertThat(result).isEqualTo(expectedDTO); + verify(colorUseCase).getCrossProfileColors("my-dataset"); + } + + @Test + void updateCrossProfileColors_invokesUseCaseWithPayload() { + var colorData = new CrossProfileDiagramColorDataDTO(Map.of("graph-a", "#00ff00")); + + controller.updateCrossProfileColors(HttpHeaders.ORIGIN, "my-dataset", colorData); + + verify(colorUseCase).replaceCrossProfileColors("my-dataset", colorData); + } +} diff --git a/backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramIDControllerTest.java b/backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramIDControllerTest.java new file mode 100644 index 00000000..4f4574d5 --- /dev/null +++ b/backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramIDControllerTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.api.controller.datasets.diagrams; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.rdfarchitect.database.DatabasePort; +import org.springframework.http.HttpHeaders; + +import java.util.UUID; + +class CrossProfileDiagramIDControllerTest { + + private DatabasePort databasePort; + private CrossProfileDiagramIDController controller; + + @BeforeEach + void setUp() { + databasePort = mock(DatabasePort.class); + controller = new CrossProfileDiagramIDController(databasePort); + } + + @Test + void getCrossProfileData_returnsUUIDStringFromDatabasePort() { + var uuid = UUID.randomUUID(); + when(databasePort.getCrossProfileDiagramUUID("my-dataset")).thenReturn(uuid); + + var result = controller.getCrossProfileRenderingData(HttpHeaders.ORIGIN, "my-dataset"); + + assertThat(result).isEqualTo(uuid.toString()); + verify(databasePort).getCrossProfileDiagramUUID("my-dataset"); + } +} diff --git a/backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRenderingRestControllerTest.java b/backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRenderingRestControllerTest.java new file mode 100644 index 00000000..0df1e895 --- /dev/null +++ b/backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRenderingRestControllerTest.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.api.controller.datasets.diagrams; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramDTO; +import org.rdfarchitect.api.dto.rendering.RenderingDataDTO; +import org.rdfarchitect.database.DatabasePort; +import org.rdfarchitect.models.cim.data.dto.CIMCollection; +import org.rdfarchitect.models.cim.rendering.RenderCIMCollectionUseCase; +import org.rdfarchitect.services.diagrams.GetCustomDiagramsUseCase; +import org.rdfarchitect.services.rendering.DiagramToCIMCollectionConverterUseCase; +import org.springframework.http.HttpHeaders; + +import java.util.List; +import java.util.UUID; + +class CrossProfileDiagramRenderingRestControllerTest { + + private DiagramToCIMCollectionConverterUseCase converter; + private RenderCIMCollectionUseCase renderer; + private GetCustomDiagramsUseCase getCustomDiagramsUseCase; + private DatabasePort databasePort; + private CrossProfileDiagramRenderingRestController controller; + + @BeforeEach + void setUp() { + converter = mock(DiagramToCIMCollectionConverterUseCase.class); + renderer = mock(RenderCIMCollectionUseCase.class); + getCustomDiagramsUseCase = mock(GetCustomDiagramsUseCase.class); + databasePort = mock(DatabasePort.class); + controller = + new CrossProfileDiagramRenderingRestController( + converter, renderer, getCustomDiagramsUseCase, databasePort); + } + + @Test + void getCrossProfileRenderingData_orchestratesUseCasesAndReturnsRenderingDTO() { + var diagramUUID = UUID.randomUUID(); + var diagram = new CrossProfileDiagramDTO(diagramUUID, List.of()); + var cimCollection = mock(CIMCollection.class); + var expectedRendering = mock(RenderingDataDTO.class); + + when(getCustomDiagramsUseCase.getCrossProfileDiagram("my-dataset", true, true)) + .thenReturn(diagram); + when(converter.convert(diagram)).thenReturn(cimCollection); + when(databasePort.getCrossProfileDiagramUUID("my-dataset")).thenReturn(diagramUUID); + when(renderer.renderGlobalUML(cimCollection, "my-dataset", diagramUUID)) + .thenReturn(expectedRendering); + + var result = controller.getCrossProfileRenderingData(HttpHeaders.ORIGIN, "my-dataset"); + + assertThat(result).isEqualTo(expectedRendering); + verify(getCustomDiagramsUseCase).getCrossProfileDiagram("my-dataset", true, true); + verify(converter).convert(diagram); + verify(databasePort).getCrossProfileDiagramUUID("my-dataset"); + verify(renderer).renderGlobalUML(cimCollection, "my-dataset", diagramUUID); + } +} diff --git a/backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestControllerTest.java b/backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestControllerTest.java new file mode 100644 index 00000000..0bcd61ed --- /dev/null +++ b/backend/src/test/java/org/rdfarchitect/api/controller/datasets/diagrams/CrossProfileDiagramRestControllerTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024-2026 SOPTIM AG + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.rdfarchitect.api.controller.datasets.diagrams; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.rdfarchitect.api.dto.crossProfileDiagram.CrossProfileDiagramDTO; +import org.rdfarchitect.services.diagrams.GetCustomDiagramsUseCase; +import org.springframework.http.HttpHeaders; + +import java.util.List; +import java.util.UUID; + +class CrossProfileDiagramRestControllerTest { + + private GetCustomDiagramsUseCase getCustomDiagramsUseCase; + private CrossProfileDiagramRestController controller; + + @BeforeEach + void setUp() { + getCustomDiagramsUseCase = mock(GetCustomDiagramsUseCase.class); + controller = new CrossProfileDiagramRestController(getCustomDiagramsUseCase); + } + + @Test + void getCrossProfileData_returnsDTOFromUseCase() { + var expectedDTO = new CrossProfileDiagramDTO(UUID.randomUUID(), List.of()); + when(getCustomDiagramsUseCase.getCrossProfileDiagram("my-dataset", false, false)) + .thenReturn(expectedDTO); + + var result = controller.getCrossProfileRenderingData(HttpHeaders.ORIGIN, "my-dataset"); + + assertThat(result).isEqualTo(expectedDTO); + verify(getCustomDiagramsUseCase).getCrossProfileDiagram("my-dataset", false, false); + } +} From cab90dcfc8268d1fd5b66d0e51d2ece9adb71c52 Mon Sep 17 00:00:00 2001 From: Philipp Date: Fri, 12 Jun 2026 11:08:39 +0200 Subject: [PATCH 24/30] updated README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8e6067e8..275b1207 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ For the complete documentation, see the [docs site](https://rdfarchitect.soptim. - Import and export RDF schemas and CIM extensions - Visualize class structures via UML diagrams -- create custom diagrams containing classes from different packages or even profiles +- Create custom diagrams containing classes from different packages or even profiles +- Merge View — Visualize and inspect classes from multiple profiles across a dataset in a single unified diagram - Edit classes, attributes, associations, enum entries, and notes - Share diagrams as a complete, read-friendly reference of classes, relations, notes, and constraints - Schema migration between CIM versions and extensions From 162dd4716b579aa1413fb6e0bcbffee5f9885535 Mon Sep 17 00:00:00 2001 From: Philipp Date: Fri, 12 Jun 2026 11:22:41 +0200 Subject: [PATCH 25/30] format --- .../classEditor/components/ClassEditorButtons.svelte | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/routes/mainpage/classEditor/components/ClassEditorButtons.svelte b/frontend/src/routes/mainpage/classEditor/components/ClassEditorButtons.svelte index ebd3b661..810a01e7 100644 --- a/frontend/src/routes/mainpage/classEditor/components/ClassEditorButtons.svelte +++ b/frontend/src/routes/mainpage/classEditor/components/ClassEditorButtons.svelte @@ -128,7 +128,9 @@ datasetOfClassToOpenNext, ); editorState.selectedClassGraph.updateValue(graphOfClassToOpenNext); - editorState.selectedClassType.updateValue(classTypeOfClassToOpenNext); + editorState.selectedClassType.updateValue( + classTypeOfClassToOpenNext, + ); editorState.selectedClassUUID.updateValue(classToOpenNext); } } @@ -137,7 +139,7 @@ saveChanges(); editorState.selectedClassDataset.updateValue(datasetOfClassToOpenNext); editorState.selectedClassGraph.updateValue(graphOfClassToOpenNext); - ditorState.selectedClassType.updateValue(classTypeOfClassToOpenNext); + editorState.selectedClassType.updateValue(classTypeOfClassToOpenNext); editorState.selectedClassUUID.updateValue(classToOpenNext); } From 33fc270162ef5a45b55975d45602896e8ab9eadc Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 17 Jun 2026 07:36:01 +0200 Subject: [PATCH 26/30] removed debug messages --- frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte | 1 - frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte | 1 - 2 files changed, 2 deletions(-) diff --git a/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte b/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte index 4b55a25d..033b5449 100644 --- a/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte +++ b/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte @@ -319,7 +319,6 @@ : ClassType.NORMAL_CLASS, ); editorState.selectedClassUUID.updateValue(id); - console.log(editorState.selectedClassType.getValue()); } else { eventStack.executeNewestEvent({ datasetName: editorState.selectedDataset.getValue(), diff --git a/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte b/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte index 08ed185d..70816f86 100644 --- a/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte @@ -82,7 +82,6 @@ } onPackChange(); if (!editorState.selectedClassUUID.getValue()) { - console.warn("here: ", { diagramId }); eventStack.executeNewestEvent(classNavEntry.id); editorState.selectedClassDataset.updateValue(datasetNavEntry.id); editorState.selectedClassGraph.updateValue(graphNavEntry.id); From e86aecad74d0437211f5009550b58def318056e8 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 17 Jun 2026 15:11:37 +0200 Subject: [PATCH 27/30] fixed single click expands merge view --- .../packageNavigation/CrossProfileDiagramsSection.svelte | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/routes/mainpage/packageNavigation/CrossProfileDiagramsSection.svelte b/frontend/src/routes/mainpage/packageNavigation/CrossProfileDiagramsSection.svelte index 8c3a8c7b..6329393e 100644 --- a/frontend/src/routes/mainpage/packageNavigation/CrossProfileDiagramsSection.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/CrossProfileDiagramsSection.svelte @@ -80,7 +80,6 @@ isSelected={isMergedViewSelected} onclick={() => { selectMergedView(); - isOpen = !isOpen; }} onToggle={() => (isOpen = !isOpen)} /> From a2a9e9d024f4d9853959a34cc918cbb4ff7b4465 Mon Sep 17 00:00:00 2001 From: Philipp Date: Wed, 17 Jun 2026 16:02:37 +0200 Subject: [PATCH 28/30] fixed enum entry mapping --- .../rdfarchitect/api/dto/enumentries/EnumEntryMapper.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/backend/src/main/java/org/rdfarchitect/api/dto/enumentries/EnumEntryMapper.java b/backend/src/main/java/org/rdfarchitect/api/dto/enumentries/EnumEntryMapper.java index 2eaa1dc0..ffa1eb14 100644 --- a/backend/src/main/java/org/rdfarchitect/api/dto/enumentries/EnumEntryMapper.java +++ b/backend/src/main/java/org/rdfarchitect/api/dto/enumentries/EnumEntryMapper.java @@ -19,6 +19,7 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; +import org.mapstruct.Named; import org.mapstruct.factory.Mappers; import org.rdfarchitect.api.dto.MappingUtils; import org.rdfarchitect.models.cim.data.dto.CIMEnumEntry; @@ -40,7 +41,7 @@ public interface EnumEntryMapper { @Mapping(target = "label", source = "label.value") @Mapping(target = "prefix", source = "uri.prefix") @Mapping(target = "comment", source = "comment.value") - @Mapping(target = "type", source = "type.uri.suffix") + @Mapping(target = "type", source = "type", qualifiedByName = "mapTypeToString") EnumEntryDTO toDTO(CIMEnumEntry entity); List toDTOList(List entityList); @@ -69,4 +70,9 @@ default RDFType buildType(EnumEntryDTO dto) { default CIMSStereotype buildStereotype(String stereotype) { return stereotype != null ? new CIMSStereotype(CIMStereotypes.enumerationString) : null; } + + @Named("mapTypeToString") + default String mapTypeToString(RDFType type) { + return type != null ? type.getUri().toString() : null; + } } From f661899ddea1ab756e9e0de9416adbf2dd0e114f Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 18 Jun 2026 08:30:03 +0200 Subject: [PATCH 29/30] adjusted tests --- .../java/org/rdfarchitect/api/dto/EnumEntryMapperTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/test/java/org/rdfarchitect/api/dto/EnumEntryMapperTest.java b/backend/src/test/java/org/rdfarchitect/api/dto/EnumEntryMapperTest.java index 4c61072b..acba30cb 100644 --- a/backend/src/test/java/org/rdfarchitect/api/dto/EnumEntryMapperTest.java +++ b/backend/src/test/java/org/rdfarchitect/api/dto/EnumEntryMapperTest.java @@ -75,7 +75,7 @@ void toDto_minimalEnumEntry() { () -> assertThat(dto.getUuid()).isEqualTo(cimEnumEntry.getUuid()), () -> assertThat(dto.getPrefix()).isEqualTo("http://example.org#"), () -> assertThat(dto.getLabel()).isEqualTo("TestEnumEntry"), - () -> assertThat(dto.getType()).isEqualTo("TestEnum"), + () -> assertThat(dto.getType()).isEqualTo("http://example.org#TestEnum"), () -> assertThat(dto.getComment()).isNull(), () -> assertThat(dto.getStereotype()).isNull()); } @@ -94,7 +94,7 @@ void toDTO_fullEnumEntry() { () -> assertThat(dto.getUuid()).isEqualTo(cimEnumEntry.getUuid()), () -> assertThat(dto.getPrefix()).isEqualTo("http://example.org#"), () -> assertThat(dto.getLabel()).isEqualTo("TestEnumEntry"), - () -> assertThat(dto.getType()).isEqualTo("TestEnum"), + () -> assertThat(dto.getType()).isEqualTo("http://example.org#TestEnum"), () -> assertThat(dto.getComment()).isEqualTo("Test comment"), () -> assertThat(dto.getStereotype()).isEqualTo("enum")); } From 2d93aea522b0ae458980113e5b642556a50f168a Mon Sep 17 00:00:00 2001 From: Philipp Date: Thu, 18 Jun 2026 09:57:14 +0200 Subject: [PATCH 30/30] selectedClassUUID and selectedClassType combined to selectedClass --- .../rendering/mermaid/mermaidWrapper.svelte | 2 +- .../svelteflow/components/ClassNode.svelte | 2 +- .../svelteflow/svelteFlowWrapper.svelte | 20 +++++++++------- frontend/src/lib/sharedState.svelte.js | 10 ++++---- frontend/src/routes/+layout.svelte | 6 ++--- frontend/src/routes/GraphDeleteDialog.svelte | 3 +-- frontend/src/routes/ImportDialog.svelte | 3 +-- frontend/src/routes/NewClassDialog.svelte | 10 ++++---- frontend/src/routes/NewGraphDialog.svelte | 2 +- frontend/src/routes/Searchbar.svelte | 15 +++++++----- frontend/src/routes/UserSettingDialog.svelte | 24 ++++--------------- .../DeleteDependenciesDialog.svelte | 3 +-- .../src/routes/layout/menu-bar/Edit.svelte | 6 ++--- .../src/routes/layout/menu-bar/View.svelte | 2 +- .../mainpage/classEditor/classEditor.svelte | 8 ++++--- .../components/ClassEditorButtons.svelte | 16 +++++++------ .../save-association-to-backend.js | 2 +- .../attributes/save-attribute-to-backend.js | 2 +- .../save-enum-entry-to-backend.js | 2 +- .../classEditor/mergedClassEditor.svelte | 3 +-- .../src/routes/mainpage/mermaidWrapper.svelte | 4 ++-- .../packageNavigation/ClassEntry.svelte | 16 +++++++------ .../PackageDeleteDialog.svelte | 2 +- .../CustomDiagramDeleteDialog.svelte | 2 +- .../packageNavigationUtils.svelte.js | 2 +- .../save-copy-class-to-backend.js | 6 ++++- .../src/routes/mainpage/packageWindow.svelte | 19 +++++++++------ .../shacl/SHACLPropertySpecificDialog.svelte | 8 +++---- .../SHACLClassSpecificPopUp.svelte | 9 ++++--- .../SHACLShapeTtlRenderer.svelte | 6 +++-- 30 files changed, 109 insertions(+), 106 deletions(-) diff --git a/frontend/src/lib/rendering/mermaid/mermaidWrapper.svelte b/frontend/src/lib/rendering/mermaid/mermaidWrapper.svelte index d3e9ad0f..51e5a6be 100644 --- a/frontend/src/lib/rendering/mermaid/mermaidWrapper.svelte +++ b/frontend/src/lib/rendering/mermaid/mermaidWrapper.svelte @@ -48,7 +48,7 @@ */ window.getClassInformation = nodeId => { console.log("selecting class: ", nodeId); - editorState.selectedClassUUID.updateValue(nodeId); + editorState.selectedClass.updateValue(nodeId); }; }); diff --git a/frontend/src/lib/rendering/svelteflow/components/ClassNode.svelte b/frontend/src/lib/rendering/svelteflow/components/ClassNode.svelte index f121423d..ed086652 100644 --- a/frontend/src/lib/rendering/svelteflow/components/ClassNode.svelte +++ b/frontend/src/lib/rendering/svelteflow/components/ClassNode.svelte @@ -25,7 +25,7 @@ let { id, data, dragging } = $props(); const highlighted = $derived( - editorState.selectedClassUUID.getValue() === id, + editorState.selectedClass.getProperty("id") === id, ); const label = $derived(data.label); diff --git a/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte b/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte index 033b5449..4bf95c35 100644 --- a/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte +++ b/frontend/src/lib/rendering/svelteflow/svelteFlowWrapper.svelte @@ -117,8 +117,8 @@ }); $effect(() => { - editorState.selectedClassUUID.subscribe(); - const selectedUUID = editorState.selectedClassUUID.getValue(); + editorState.selectedClass.subscribe(); + const selectedUUID = editorState.selectedClass.getProperty("id"); untrack(() => { if (!selectedUUID) { resetTemporaryFront(); @@ -304,7 +304,7 @@ bringToFrontTemporarily(id); - if (!editorState.selectedClassUUID.getValue()) { + if (!editorState.selectedClass.getProperty("id")) { eventStack.executeNewestEvent(id); editorState.selectedClassDataset.updateValue( editorState.selectedDataset.getValue(), @@ -312,13 +312,15 @@ editorState.selectedClassGraph.updateValue( nodeClickEvent.node.data.graphUri, ); - editorState.selectedClassType.updateValue( + const classType = editorState.selectedDiagram.getProperty("type") === - DiagramType.CROSS_PROFILE + DiagramType.CROSS_PROFILE ? ClassType.MERGED_CLASS - : ClassType.NORMAL_CLASS, - ); - editorState.selectedClassUUID.updateValue(id); + : ClassType.SINGLE_CLASS; + editorState.selectedClass.updateValue({ + type: classType, + id: id, + }); } else { eventStack.executeNewestEvent({ datasetName: editorState.selectedDataset.getValue(), @@ -328,7 +330,7 @@ editorState.selectedDiagram.getProperty("type") === DiagramType.CROSS_PROFILE ? ClassType.MERGED_CLASS - : ClassType.NORMAL_CLASS, + : ClassType.SINGLE_CLASS, }); } diff --git a/frontend/src/lib/sharedState.svelte.js b/frontend/src/lib/sharedState.svelte.js index 156afec9..9f0a4311 100644 --- a/frontend/src/lib/sharedState.svelte.js +++ b/frontend/src/lib/sharedState.svelte.js @@ -42,7 +42,7 @@ export const DiagramType = { }; export const ClassType = { - NORMAL_CLASS: "normalClass", + SINGLE_CLASS: "singleClass", MERGED_CLASS: "mergedClass", }; @@ -54,7 +54,7 @@ export const ClassType = { * selectedDiagram: StateObjectPair, * selectedClassDataset: StateValuePair, * selectedClassGraph: StateValuePair, - * selectedClassUUID: StateValuePair, + * selectedClass: StateObjectPair, * focusedClassUUID: StateValuePair, * selectedContext: StateValuePair, * reset: () => void @@ -67,8 +67,7 @@ export const editorState = { selectedDiagram: new StateObjectPair({ type: null, id: null }), selectedClassDataset: new StateValuePair(), selectedClassGraph: new StateValuePair(), - selectedClassType: new StateValuePair(), - selectedClassUUID: new StateValuePair(), + selectedClass: new StateObjectPair({ type: null, id: null }), focusedClassUUID: new StateValuePair(), selectedContext: new StateValuePair(), @@ -78,8 +77,7 @@ export const editorState = { this.selectedDiagram.updateValue({ type: null, id: null }); this.selectedClassDataset.updateValue(null); this.selectedClassGraph.updateValue(null); - this.selectedClassType.updateValue(null); - this.selectedClassUUID.updateValue(null); + this.selectedClass.updateValue({ type: null, id: null }); this.focusedClassUUID.updateValue(null); this.selectedContext.updateValue(null); }, diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index f22cf74d..5b549839 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -70,7 +70,7 @@ $effect(async () => { editorState.selectedDiagram.subscribe(); - editorState.selectedClassUUID.subscribe(); + editorState.selectedClass.subscribe(); editorState.selectedGraph.subscribe(); editorState.selectedDataset.subscribe(); forceReloadTrigger.subscribe(); @@ -94,7 +94,7 @@ return; } forceReloadTrigger.trigger(); - editorState.selectedClassUUID.trigger(); + editorState.selectedClass.trigger(); editorState.selectedDiagram.trigger(); isDatasetReadOnly = false; } @@ -132,7 +132,7 @@ await fetchUndoRedo(); editorState.selectedDataset.trigger(); editorState.selectedGraph.trigger(); - editorState.selectedClassUUID.trigger(); + editorState.selectedClass.trigger(); forceReloadTrigger.trigger(); } diff --git a/frontend/src/routes/GraphDeleteDialog.svelte b/frontend/src/routes/GraphDeleteDialog.svelte index 85353a11..12511866 100644 --- a/frontend/src/routes/GraphDeleteDialog.svelte +++ b/frontend/src/routes/GraphDeleteDialog.svelte @@ -68,8 +68,7 @@ }); editorState.selectedClassDataset.updateValue(null); editorState.selectedClassGraph.updateValue(null); - editorState.selectedClassType.updateValue(null); - editorState.selectedClassUUID.updateValue(null); + editorState.selectedClass.updateValue({ type: null, id: null }); toastStore.success( "Schema deleted", `"${deletedGraph}" was removed.`, diff --git a/frontend/src/routes/ImportDialog.svelte b/frontend/src/routes/ImportDialog.svelte index e4397272..987ce4ed 100644 --- a/frontend/src/routes/ImportDialog.svelte +++ b/frontend/src/routes/ImportDialog.svelte @@ -241,8 +241,7 @@ editorState.selectedDiagram.updateValue({ type: null, id: null }); editorState.selectedClassDataset.updateValue(null); editorState.selectedClassGraph.updateValue(null); - editorState.selectedClassType.updateValue(null); - editorState.selectedClassUUID.updateValue(null); + editorState.selectedClass.updateValue({ type: null, id: null }); const importedCount = importedGraphUris.length; const summary = `${importedCount} graph${importedCount === 1 ? "" : "s"} imported into "${datasetName}".`; diff --git a/frontend/src/routes/NewClassDialog.svelte b/frontend/src/routes/NewClassDialog.svelte index 7f9848f9..6de86d91 100644 --- a/frontend/src/routes/NewClassDialog.svelte +++ b/frontend/src/routes/NewClassDialog.svelte @@ -236,10 +236,10 @@ }); editorState.selectedClassDataset.updateValue(datasetNameLocal); editorState.selectedClassGraph.updateValue(graphURILocal); - editorState.selectedClassType.updateValue( - ClassType.NORMAL_CLASS, - ); - editorState.selectedClassUUID.updateValue(uuid); + editorState.selectedClass.updateValue({ + type: ClassType.SINGLE_CLASS, + id: uuid, + }); toastStore.success( "Class created", `"${classNameLocal.value}" was added.`, @@ -262,7 +262,7 @@ editorState.selectedDataset.trigger(); editorState.selectedGraph.trigger(); editorState.selectedDiagram.trigger(); - editorState.selectedClassUUID.trigger(); + editorState.selectedClass.trigger(); } } diff --git a/frontend/src/routes/NewGraphDialog.svelte b/frontend/src/routes/NewGraphDialog.svelte index fa651d09..6c9512de 100644 --- a/frontend/src/routes/NewGraphDialog.svelte +++ b/frontend/src/routes/NewGraphDialog.svelte @@ -130,7 +130,7 @@ type: DiagramType.PACKAGE, id: "default", }); - editorState.selectedClassUUID.updateValue(null); + editorState.selectedClass.updateValue({ type: null, id: null }); toastStore.success( "Schema created", `"${graphURILocal}" was added to "${datasetNameLocal}".`, diff --git a/frontend/src/routes/Searchbar.svelte b/frontend/src/routes/Searchbar.svelte index 20cd1e9a..dba52f38 100644 --- a/frontend/src/routes/Searchbar.svelte +++ b/frontend/src/routes/Searchbar.svelte @@ -78,11 +78,13 @@ searchResult.datasetName, ); editorState.selectedClassGraph.updateValue(searchResult.graphUri); - editorState.selectedClassType.updateValue(ClassType.NORMAL_CLASS); - editorState.selectedClassUUID.updateValue(searchResult.uuid); + editorState.selectedClass.updateValue({ + type: ClassType.SINGLE_CLASS, + id: searchResult.uuid, + }); editorState.focusedClassUUID.updateValue(searchResult.uuid); } else if (searchResult.type === "PACKAGE") { - editorState.selectedClassUUID.updateValue(null); + editorState.selectedClass.updateValue({ type: null, id: null }); editorState.focusedClassUUID.updateValue(null); editorState.selectedDiagram.updateValue({ type: DiagramType.PACKAGE, @@ -93,9 +95,10 @@ searchResult.datasetName, ); editorState.selectedClassGraph.updateValue(searchResult.graphUri); - editorState.selectedClassUUID.updateValue( - searchResult.parentClassUUID, - ); + editorState.selectedClass.updateValue({ + type: ClassType.SINGLE_CLASS, + id: searchResult.parentClassUUID, + }); editorState.focusedClassUUID.updateValue( searchResult.parentClassUUID, ); diff --git a/frontend/src/routes/UserSettingDialog.svelte b/frontend/src/routes/UserSettingDialog.svelte index 6737ca75..7a04e2df 100644 --- a/frontend/src/routes/UserSettingDialog.svelte +++ b/frontend/src/routes/UserSettingDialog.svelte @@ -71,11 +71,7 @@ - (localSettings["usePackagePrefix"] = true)} - callOnInputFalse={() => - (localSettings["usePackagePrefix"] = false)} + bind:value={localSettings["usePackagePrefix"]} labelFirst={false} /> - (localSettings["showPackagePrefix"] = true)} - callOnInputFalse={() => - (localSettings["showPackagePrefix"] = false)} + bind:value={localSettings["showPackagePrefix"]} labelFirst={false} /> - (localSettings["useColoredPropertiesInMergedView"] = true)} - callOnInputFalse={() => - (localSettings["useColoredPropertiesInMergedView"] = false)} + bind:value={localSettings["useColoredPropertiesInMergedView"]} labelFirst={false} /> - (localSettings["normalizeComments"] = true)} - callOnInputFalse={() => - (localSettings["normalizeComments"] = false)} + bind:value={localSettings["normalizeComments"]} labelFirst={false} /> diff --git a/frontend/src/routes/delete-relations-dialog/DeleteDependenciesDialog.svelte b/frontend/src/routes/delete-relations-dialog/DeleteDependenciesDialog.svelte index 121e4dee..c2f2f4b3 100644 --- a/frontend/src/routes/delete-relations-dialog/DeleteDependenciesDialog.svelte +++ b/frontend/src/routes/delete-relations-dialog/DeleteDependenciesDialog.svelte @@ -162,8 +162,7 @@ forceReloadTrigger.trigger(); editorState.selectedClassDataset.updateValue(null); editorState.selectedClassGraph.updateValue(null); - editorState.selectedClassType.updateValue(null); - editorState.selectedClassUUID.updateValue(null); + editorState.selectedClass.updateValue({ type: null, id: null }); toastStore.success( `${type ? type.charAt(0).toUpperCase() + type.slice(1) : "Resource"} deleted`, label ? `"${label}" was removed.` : "Resource was removed.", diff --git a/frontend/src/routes/layout/menu-bar/Edit.svelte b/frontend/src/routes/layout/menu-bar/Edit.svelte index 33aa3bfb..a0ebe06f 100644 --- a/frontend/src/routes/layout/menu-bar/Edit.svelte +++ b/frontend/src/routes/layout/menu-bar/Edit.svelte @@ -109,7 +109,7 @@ let graphHasOntology = $derived(!!ontology); let disableCopyClassButton = $derived( - !editorState.selectedClassUUID.getValue(), + !editorState.selectedClass.getProperty("id"), ); let disablePasteButton = $derived( isDatasetReadOnly || @@ -122,7 +122,7 @@ $effect(async () => { editorState.selectedDiagram.subscribe(); - editorState.selectedClassUUID.subscribe(); + editorState.selectedClass.subscribe(); editorState.selectedGraph.subscribe(); editorState.selectedDataset.subscribe(); forceReloadTrigger.subscribe(); @@ -314,7 +314,7 @@ function copyClass() { copyState.classUUID.updateValue( - editorState.selectedClassUUID.getValue(), + editorState.selectedClass.getProperty("id"), ); copyState.graphURI.updateValue( editorState.selectedClassGraph.getValue(), diff --git a/frontend/src/routes/layout/menu-bar/View.svelte b/frontend/src/routes/layout/menu-bar/View.svelte index dbc265df..b51be16d 100644 --- a/frontend/src/routes/layout/menu-bar/View.svelte +++ b/frontend/src/routes/layout/menu-bar/View.svelte @@ -47,7 +47,7 @@ $effect(async () => { editorState.selectedDiagram.subscribe(); - editorState.selectedClassUUID.subscribe(); + editorState.selectedClass.subscribe(); editorState.selectedGraph.subscribe(); editorState.selectedDataset.subscribe(); forceReloadTrigger.subscribe(); diff --git a/frontend/src/routes/mainpage/classEditor/classEditor.svelte b/frontend/src/routes/mainpage/classEditor/classEditor.svelte index f7a20bf1..fa411a6e 100644 --- a/frontend/src/routes/mainpage/classEditor/classEditor.svelte +++ b/frontend/src/routes/mainpage/classEditor/classEditor.svelte @@ -92,7 +92,7 @@ ); $effect(() => { - editorState.selectedClassUUID.subscribe(); + editorState.selectedClass.subscribe(); forceReloadTrigger.subscribe(); const cancellation = { cancelled: false }; @@ -186,8 +186,10 @@ } editorState.selectedClassDataset.updateValue(datasetName); editorState.selectedClassGraph.updateValue(graphUri); - editorState.selectedClassType.updateValue(classType); - editorState.selectedClassUUID.updateValue(classUuid); + editorState.selectedClass.updateValue({ + type: classType, + id: classUuid, + }); } async function loadContext() { diff --git a/frontend/src/routes/mainpage/classEditor/components/ClassEditorButtons.svelte b/frontend/src/routes/mainpage/classEditor/components/ClassEditorButtons.svelte index c5632218..ceb3cbed 100644 --- a/frontend/src/routes/mainpage/classEditor/components/ClassEditorButtons.svelte +++ b/frontend/src/routes/mainpage/classEditor/components/ClassEditorButtons.svelte @@ -121,7 +121,7 @@ responseText, ); reactiveClass.save(); - editorState.selectedClassUUID.trigger(); + editorState.selectedClass.trigger(); editorState.selectedDiagram.trigger(); forceReloadTrigger.trigger(); toastStore.success("Class saved", `"${classLabel}" was saved.`); @@ -158,10 +158,10 @@ datasetOfClassToOpenNext, ); editorState.selectedClassGraph.updateValue(graphOfClassToOpenNext); - editorState.selectedClassType.updateValue( - classTypeOfClassToOpenNext, - ); - editorState.selectedClassUUID.updateValue(classToOpenNext); + editorState.selectedClass.updateValue({ + type: classTypeOfClassToOpenNext, + id: classToOpenNext, + }); } } @@ -169,8 +169,10 @@ saveChanges(); editorState.selectedClassDataset.updateValue(datasetOfClassToOpenNext); editorState.selectedClassGraph.updateValue(graphOfClassToOpenNext); - editorState.selectedClassType.updateValue(classTypeOfClassToOpenNext); - editorState.selectedClassUUID.updateValue(classToOpenNext); + editorState.selectedClass.updateValue({ + type: classTypeOfClassToOpenNext, + id: classToOpenNext, + }); } diff --git a/frontend/src/routes/mainpage/classEditor/components/associations/save-association-to-backend.js b/frontend/src/routes/mainpage/classEditor/components/associations/save-association-to-backend.js index 47fe9c74..feb55c86 100644 --- a/frontend/src/routes/mainpage/classEditor/components/associations/save-association-to-backend.js +++ b/frontend/src/routes/mainpage/classEditor/components/associations/save-association-to-backend.js @@ -48,7 +48,7 @@ export function saveApiAssociationToBackend( return { ok: false, errorText }; }) .finally(() => { - editorState.selectedClassUUID.trigger(); + editorState.selectedClass.trigger(); editorState.selectedDiagram.trigger(); }); } diff --git a/frontend/src/routes/mainpage/classEditor/components/attributes/save-attribute-to-backend.js b/frontend/src/routes/mainpage/classEditor/components/attributes/save-attribute-to-backend.js index 9cff6ca3..b38f747d 100644 --- a/frontend/src/routes/mainpage/classEditor/components/attributes/save-attribute-to-backend.js +++ b/frontend/src/routes/mainpage/classEditor/components/attributes/save-attribute-to-backend.js @@ -44,7 +44,7 @@ export async function saveApiAttributeToBackend( console.error("Could not save attribute:", errorText); return { ok: false, errorText }; } finally { - editorState.selectedClassUUID.trigger(); + editorState.selectedClass.trigger(); editorState.selectedDiagram.trigger(); } } diff --git a/frontend/src/routes/mainpage/classEditor/components/enum-entries/save-enum-entry-to-backend.js b/frontend/src/routes/mainpage/classEditor/components/enum-entries/save-enum-entry-to-backend.js index efc641f7..c05139af 100644 --- a/frontend/src/routes/mainpage/classEditor/components/enum-entries/save-enum-entry-to-backend.js +++ b/frontend/src/routes/mainpage/classEditor/components/enum-entries/save-enum-entry-to-backend.js @@ -44,7 +44,7 @@ export async function saveApiEnumEntryToBackend( console.error("Could not save enum entry:", errorText); return { ok: false, errorText }; } finally { - editorState.selectedClassUUID.trigger(); + editorState.selectedClass.trigger(); editorState.selectedDiagram.trigger(); } } diff --git a/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte b/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte index 68cb35c6..49ac088f 100644 --- a/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte +++ b/frontend/src/routes/mainpage/classEditor/mergedClassEditor.svelte @@ -57,8 +57,7 @@ function closeMergedClassEditor() { editorState.selectedClassDataset.updateValue(null); editorState.selectedClassGraph.updateValue(null); - editorState.selectedClassType.updateValue(null); - editorState.selectedClassUUID.updateValue(null); + editorState.selectedClass.updateValue({ type: null, id: null }); } diff --git a/frontend/src/routes/mainpage/mermaidWrapper.svelte b/frontend/src/routes/mainpage/mermaidWrapper.svelte index 27b103f7..d544e288 100644 --- a/frontend/src/routes/mainpage/mermaidWrapper.svelte +++ b/frontend/src/routes/mainpage/mermaidWrapper.svelte @@ -84,7 +84,7 @@ */ window.getClassInformation = nodeId => { console.log("selecting class: ", nodeId); - if (!editorState.selectedClassUUID.getValue()) { + if (!editorState.selectedClass.getProperty("id")) { eventStack.executeNewestEvent(nodeId); editorState.selectedClassDataset.updateValue( editorState.selectedDataset.getValue(), @@ -92,7 +92,7 @@ editorState.selectedClassGraph.updateValue( editorState.selectedGraph.getValue(), ); - editorState.selectedClassUUID.updateValue(nodeId); + editorState.selectedClass.updateValue(nodeId); return; } eventStack.executeNewestEvent({ diff --git a/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte b/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte index 70816f86..5c4d4ff8 100644 --- a/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/ClassEntry.svelte @@ -55,7 +55,7 @@ namespaces = [], readonly = false, onPackChange = () => {}, - classType = ClassType.NORMAL_CLASS, + classType = ClassType.SINGLE_CLASS, diagramType = DiagramType.PACKAGE, } = $props(); @@ -81,12 +81,14 @@ classNavEntry.parent?.open(); } onPackChange(); - if (!editorState.selectedClassUUID.getValue()) { + if (!editorState.selectedClass.getProperty("id")) { eventStack.executeNewestEvent(classNavEntry.id); editorState.selectedClassDataset.updateValue(datasetNavEntry.id); editorState.selectedClassGraph.updateValue(graphNavEntry.id); - editorState.selectedClassType.updateValue(classType); - editorState.selectedClassUUID.updateValue(classNavEntry.id); + editorState.selectedClass.updateValue({ + type: classType, + id: classNavEntry.id, + }); return; } //The event executed to open the discard confirm delete dialog @@ -109,7 +111,7 @@ function showClassInPackage() { editorState.selectedDataset.updateValue(datasetNavEntry.id); editorState.selectedGraph.updateValue(graphNavEntry.id); - editorState.selectedClassType.updateValue(classType); + //editorState.selectedClassType.updateValue(classType); editorState.selectedDiagram.updateValue({ type: diagramType, id: classNavEntry.parent?.id ?? "default", @@ -142,7 +144,7 @@ /> - {#if classType === ClassType.NORMAL_CLASS} + {#if classType === ClassType.SINGLE_CLASS} Show in diagram - {#if classType === ClassType.NORMAL_CLASS} + {#if classType === ClassType.SINGLE_CLASS} { showSHACLDialog = true; diff --git a/frontend/src/routes/mainpage/packageNavigation/PackageDeleteDialog.svelte b/frontend/src/routes/mainpage/packageNavigation/PackageDeleteDialog.svelte index 6c0284de..04f42125 100644 --- a/frontend/src/routes/mainpage/packageNavigation/PackageDeleteDialog.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/PackageDeleteDialog.svelte @@ -56,7 +56,7 @@ }); editorState.selectedClassDataset.updateValue(null); editorState.selectedClassGraph.updateValue(null); - editorState.selectedClassUUID.updateValue(null); + editorState.selectedClass.updateValue({ type: null, id: null }); } toastStore.success( "Package deleted", diff --git a/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomDiagramDeleteDialog.svelte b/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomDiagramDeleteDialog.svelte index 60486ae7..6ac90152 100644 --- a/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomDiagramDeleteDialog.svelte +++ b/frontend/src/routes/mainpage/packageNavigation/custom-diagram-dialogs/CustomDiagramDeleteDialog.svelte @@ -66,7 +66,7 @@ }); editorState.selectedClassDataset.updateValue(null); editorState.selectedClassGraph.updateValue(null); - editorState.selectedClassUUID.updateValue(null); + editorState.selectedClass.updateValue({ type: null, id: null }); } toastStore.success( "Custom diagram deleted", diff --git a/frontend/src/routes/mainpage/packageNavigation/packageNavigationUtils.svelte.js b/frontend/src/routes/mainpage/packageNavigation/packageNavigationUtils.svelte.js index 70f990bc..4f57a5ab 100644 --- a/frontend/src/routes/mainpage/packageNavigation/packageNavigationUtils.svelte.js +++ b/frontend/src/routes/mainpage/packageNavigation/packageNavigationUtils.svelte.js @@ -55,7 +55,7 @@ export function isSelectedClass(dataset, graph, cls) { const datasetLabel = dataset?.label ?? dataset; const graphUri = graph ? getUri(graph) : null; return ( - editorState.selectedClassUUID.getValue() === cls.uuid && + editorState.selectedClass.getProperty("id") === cls.uuid && editorState.selectedClassDataset.getValue() === datasetLabel && editorState.selectedClassGraph.getValue() === graphUri ); diff --git a/frontend/src/routes/mainpage/packageNavigation/save-copy-class-to-backend.js b/frontend/src/routes/mainpage/packageNavigation/save-copy-class-to-backend.js index 12e0a357..7d3d30ad 100644 --- a/frontend/src/routes/mainpage/packageNavigation/save-copy-class-to-backend.js +++ b/frontend/src/routes/mainpage/packageNavigation/save-copy-class-to-backend.js @@ -19,6 +19,7 @@ import { BackendConnection } from "$lib/api/backend.js"; import { PUBLIC_BACKEND_URL } from "$lib/config/runtime.js"; import { toastStore } from "$lib/eventhandling/toastStore.svelte.js"; import { + ClassType, copyState, DiagramType, editorState, @@ -64,7 +65,10 @@ export async function saveCopyClass( type: DiagramType.PACKAGE, id: packageDTO?.uuid ?? "default", }); - editorState.selectedClassUUID.updateValue(uuid); + editorState.selectedClass.updateValue({ + type: ClassType.SINGLE_CLASS, + id: uuid, + }); toastStore.success("Class pasted", `"${name}" was pasted.`); } else { const errorText = await res.text(); diff --git a/frontend/src/routes/mainpage/packageWindow.svelte b/frontend/src/routes/mainpage/packageWindow.svelte index 70572e9b..f34c240e 100644 --- a/frontend/src/routes/mainpage/packageWindow.svelte +++ b/frontend/src/routes/mainpage/packageWindow.svelte @@ -38,20 +38,21 @@ const selectionTrigger = $derived([ editorState.selectedDiagram.subscribe(), - editorState.selectedClassUUID.subscribe(), + editorState.selectedClass.subscribe(), ]); const isClassSelected = $derived( - selectionTrigger && !!editorState.selectedClassUUID.getValue(), + selectionTrigger && !!editorState.selectedClass.getProperty("id"), ); const classEditorKey = $derived( - `${classDatasetName ?? ""}::${classGraphUri ?? ""}::${editorState.selectedClassUUID.getValue() ?? ""}::${editorState.selectedClassUUID.subscribe()}`, + `${classDatasetName ?? ""}::${classGraphUri ?? ""}::${editorState.selectedClass.getProperty("id") ?? ""}::${editorState.selectedClass.subscribe()}`, ); const renderingKey = $derived( `${editorState.selectedDataset.getValue() ?? ""}::${editorState.selectedGraph.getValue() ?? ""}::${editorState.selectedDiagram.getProperty("id") ?? ""}`, ); const isMergedClass = $derived( - editorState.selectedClassType.getValue() === ClassType.MERGED_CLASS, + editorState.selectedClass.getProperty("type") === + ClassType.MERGED_CLASS, ); $effect(() => { @@ -78,7 +79,7 @@ function handleSplitPaneResize(event) { if (event.detail && event.detail.length > 1) { - if (!editorState.selectedClassUUID.getValue()) { + if (!editorState.selectedClass.getProperty("id")) { return; } classEditorPaneWidth = event.detail[1].size; @@ -120,13 +121,17 @@ {#if isMergedClass} {:else} {/if} {/key} diff --git a/frontend/src/routes/shacl/SHACLPropertySpecificDialog.svelte b/frontend/src/routes/shacl/SHACLPropertySpecificDialog.svelte index a4bd80e8..02476ac1 100644 --- a/frontend/src/routes/shacl/SHACLPropertySpecificDialog.svelte +++ b/frontend/src/routes/shacl/SHACLPropertySpecificDialog.svelte @@ -48,11 +48,11 @@ ); function onOpen() { - if (!editorState.selectedClassUUID.getValue() || !property) { + if (!editorState.selectedClass.getProperty("id") || !property) { return; } fetchShacl( - editorState.selectedClassUUID.getValue(), + editorState.selectedClass.getProperty("id"), property.uuid.value, ); } @@ -170,7 +170,7 @@ buildBaseUrl() + "/classes/" + encodeURIComponent( - editorState.selectedClassUUID.getValue(), + editorState.selectedClass.getProperty("id"), ) + "/" + type + @@ -204,7 +204,7 @@ ); } finally { await fetchShacl( - editorState.selectedClassUUID.getValue(), + editorState.selectedClass.getProperty("id"), property.uuid.value, ); } diff --git a/frontend/src/routes/shacl/shaclclassspecific/SHACLClassSpecificPopUp.svelte b/frontend/src/routes/shacl/shaclclassspecific/SHACLClassSpecificPopUp.svelte index 802424dc..4f3a4426 100644 --- a/frontend/src/routes/shacl/shaclclassspecific/SHACLClassSpecificPopUp.svelte +++ b/frontend/src/routes/shacl/shaclclassspecific/SHACLClassSpecificPopUp.svelte @@ -19,7 +19,7 @@ import ButtonControl from "$lib/components/ButtonControl.svelte"; import { PUBLIC_BACKEND_URL } from "$lib/config/runtime"; import ActionDialog from "$lib/dialog/ActionDialog.svelte"; - import { editorState } from "$lib/sharedState.svelte.js"; + import { ClassType, editorState } from "$lib/sharedState.svelte.js"; import ClassReferencedVia from "./ClassReferencedVia.svelte"; import SHACLShapeTtlRenderer from "./SHACLShapeTtlRenderer.svelte"; @@ -92,7 +92,10 @@ function goToClass(classUUID) { editorState.selectedClassDataset.updateValue(datasetName); editorState.selectedClassGraph.updateValue(graphUri); - editorState.selectedClassUUID.updateValue(classUUID); + editorState.selectedClass.updateValue({ + type: ClassType.SINGLE_CLASS, + id: classUUID, + }); showDialog = false; } @@ -157,7 +160,7 @@
diff --git a/frontend/src/routes/shacl/shaclclassspecific/SHACLShapeTtlRenderer.svelte b/frontend/src/routes/shacl/shaclclassspecific/SHACLShapeTtlRenderer.svelte index 5c542fec..5255f8c8 100644 --- a/frontend/src/routes/shacl/shaclclassspecific/SHACLShapeTtlRenderer.svelte +++ b/frontend/src/routes/shacl/shaclclassspecific/SHACLShapeTtlRenderer.svelte @@ -86,7 +86,9 @@ "/graphs/" + encodeURIComponent(classGraphUri) + "/classes/" + - encodeURIComponent(editorState.selectedClassUUID.getValue()) + + encodeURIComponent( + editorState.selectedClass.getProperty("id"), + ) + "/shacl/custom", { method: "PUT", @@ -116,7 +118,7 @@ ); }) .finally(() => { - editorState.selectedClassUUID.trigger(); + editorState.selectedClass.trigger(); }); }