Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
816ce63
Added feature to copy classes
kiph-soptim Apr 27, 2026
784e4b0
Merge remote-tracking branch 'origin/main' into feature/RDFA-381-Copy…
kiph-soptim Apr 27, 2026
7a5bc1c
fixed missing constructor arguments
kiph-soptim Apr 27, 2026
4ad63b6
format
kiph-soptim Apr 28, 2026
7a80654
format
kiph-soptim Apr 28, 2026
0ead7c4
format
kiph-soptim Apr 28, 2026
91aa558
format
kiph-soptim Apr 28, 2026
28ad92e
added test and refactoring
kiph-soptim Apr 28, 2026
f169a67
open pasted class automatically
kiph-soptim Apr 28, 2026
25578ae
format
kiph-soptim Apr 28, 2026
96a2fae
various improvements and fixes
kiph-soptim Apr 30, 2026
4d36a14
added a Test for the GetPackageUseCase
kiph-soptim Apr 30, 2026
6a1130b
Merge branch 'main' into feature/RDFA-381-Copy-Paste-of-classes
kiph-soptim Apr 30, 2026
e70af04
moved copyClass from svelteFlowWrapper.svelte to SvelteFlowClassConte…
kiph-soptim Apr 30, 2026
e3d8eb6
small fix
kiph-soptim May 4, 2026
690ce97
Merge branch 'main' into feature/RDFA-381-Copy-Paste-of-classes
kiph-soptim May 4, 2026
de8878a
fix: show diagram class context menu in read only mode
spah-soptim May 5, 2026
740fb96
enabled copy in read only datasets
kiph-soptim May 6, 2026
3565612
added option to copy association with the class
kiph-soptim May 6, 2026
4727bdb
fix
kiph-soptim May 6, 2026
da5a696
Merge remote-tracking branch 'origin/main' into feature/RDFA-381-Copy…
kiph-soptim May 7, 2026
b1410ba
copying a class now only creates one commit on the graph
kiph-soptim May 7, 2026
11cd68c
Merge branch 'main' into feature/RDFA-381-Copy-Paste-of-classes
kiph-soptim May 7, 2026
65c4dfb
fixed bugs
kiph-soptim May 8, 2026
54a5f11
format
kiph-soptim May 8, 2026
4d7ba1c
Merge branch 'main' into feature/RDFA-381-Copy-Paste-of-classes
kiph-soptim May 8, 2026
28cc388
removed unused variables
kiph-soptim May 8, 2026
7ecd05a
removed unused variables
kiph-soptim May 8, 2026
9dc0410
bug fixes and tests
kiph-soptim May 11, 2026
8c2f925
format
kiph-soptim May 11, 2026
8e47ecf
bug fixes
kiph-soptim May 11, 2026
13bae89
format
kiph-soptim May 11, 2026
1d8d20b
Merge branch 'main' into feature/RDFA-381-Copy-Paste-of-classes
kiph-soptim May 15, 2026
3ccb232
readability
kiph-soptim May 15, 2026
f6f0202
fixed tests
kiph-soptim May 15, 2026
54dd4b6
format
kiph-soptim May 15, 2026
26e7aec
bug fixes
kiph-soptim May 18, 2026
44499cd
bug fixes
kiph-soptim May 18, 2026
2927fab
bug fixes
kiph-soptim May 19, 2026
18d054a
bug fixes and keyboard shortcuts
kiph-soptim May 19, 2026
2d62684
Merge branch 'main' into feature/RDFA-381-Copy-Paste-of-classes
kiph-soptim May 20, 2026
b34e9dd
Merge branch 'main' into feature/RDFA-381-Copy-Paste-of-classes
kiph-soptim May 20, 2026
7aa5dc7
format
kiph-soptim May 20, 2026
4d017ee
resolved merge bugs
kiph-soptim May 20, 2026
a9669c6
bug fixes
kiph-soptim May 21, 2026
ee63dc7
Merge branch 'main' into feature/RDFA-381-Copy-Paste-of-classes
kiph-soptim May 21, 2026
a47853e
format
kiph-soptim May 21, 2026
af39512
fixed pasting into default package
kiph-soptim May 26, 2026
9e052b4
fixed alternate keyboard inputs
kiph-soptim May 26, 2026
4b3a4ca
Merge branch 'main' into feature/RDFA-381-Copy-Paste-of-classes
kiph-soptim May 27, 2026
1ca03cc
format
kiph-soptim May 27, 2026
790192e
added toast notification for pasting a class
kiph-soptim May 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* 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.graphs.classes.copy;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;

import lombok.RequiredArgsConstructor;

import org.rdfarchitect.api.dto.CopyClassRequestDTO;
import org.rdfarchitect.api.dto.CopyClassResponseDTO;
import org.rdfarchitect.database.GraphIdentifier;
import org.rdfarchitect.services.ExpandURIUseCase;
import org.rdfarchitect.services.update.classes.CopyClassUseCase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
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;

import java.util.UUID;

@RestController
@RequestMapping("api/datasets/{datasetName}/graphs/{graphURI}/classes/{classUUID}/copy")
@RequiredArgsConstructor
public class CopyClassRESTController {

private static final Logger logger = LoggerFactory.getLogger(CopyClassRESTController.class);

private final ExpandURIUseCase expandURIUseCase;
private final CopyClassUseCase copyClassUseCase;

@Operation(
summary = "copy a class",
description = "Create a copy of a class in the specified graph")
@PostMapping
public CopyClassResponseDTO copyClass(
@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,
@Parameter(
description =
"The url encoded uri of the graph, or \"default\" to access the default graph.")
@PathVariable
String graphURI,
@Parameter(description = "The uuid of the class.") @PathVariable String classUUID,
@io.swagger.v3.oas.annotations.parameters.RequestBody(
required = true,
description =
"Contains the information of the target where the class should be copied to. Also includes if the class should be copied only abstract or "
+ "fully.")
@RequestBody
CopyClassRequestDTO copyClassRequest) {
logger.info(
"Received POST request: \"/api/datasets/{{}}/graphs/{{}}/classes/{{}}/copy\" from \"{}\".",
datasetName,
graphURI,
classUUID,
originURL);

var extendedGraphURI = expandURIUseCase.expandUri(datasetName, graphURI);
var graphIdentifier = new GraphIdentifier(datasetName, extendedGraphURI);

var targetExtendedGraphURI =
expandURIUseCase.expandUri(
copyClassRequest.getTargetDatasetName(),
copyClassRequest.getTargetGraphURI());
var targetGraphIdentifier =
new GraphIdentifier(
copyClassRequest.getTargetDatasetName(), targetExtendedGraphURI);

var response =
copyClassUseCase.copyClass(
graphIdentifier,
UUID.fromString(classUUID),
targetGraphIdentifier,
copyClassRequest);

logger.info(
"Sending response to POST request: \"/api/datasets/{{}}/graphs/{{}}/classes/{{}}/copy\" to \"{}\".",
datasetName,
graphURI,
classUUID,
originURL);

return response;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@
import org.rdfarchitect.database.GraphIdentifier;
import org.rdfarchitect.services.ExpandURIUseCase;
import org.rdfarchitect.services.update.packages.DeletePackageUseCase;
import org.rdfarchitect.services.update.packages.GetPackageUseCase;
import org.rdfarchitect.services.update.packages.ReplacePackageUseCase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.DeleteMapping;
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;
Expand All @@ -51,6 +53,7 @@ public class PackageRESTController {
private final ExpandURIUseCase expandURIUseCase;
private final ReplacePackageUseCase replacePackageUseCase;
private final DeletePackageUseCase deletePackageUseCase;
private final GetPackageUseCase getPackageUseCase;

@Operation(
summary = "replace package",
Expand Down Expand Up @@ -141,4 +144,47 @@ public String deletePackage(
originURL);
return Response.SUCCESS;
}

@Operation(
summary = "get package",
description = "Returns the package DTO for the given UUID.",
tags = {"package", "graph"})
@GetMapping
public PackageDTO getPackage(
@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,
@Parameter(
description =
"The url encoded uri of the graph, or \"default\" to access the default graph.")
@PathVariable
String graphURI,
@Parameter(description = "The UUID of the package to retrieve.") @PathVariable
UUID packageUUID) {

logger.info(
"Received GET request: \"/api/datasets/{{}}/graphs/{{}}/packages/{{}}\" from \"{}\".",
datasetName,
graphURI,
packageUUID,
originURL);

var extendedGraphURI = expandURIUseCase.expandUri(datasetName, graphURI);
var graphIdentifier = new GraphIdentifier(datasetName, extendedGraphURI);

var result = getPackageUseCase.getPackage(graphIdentifier, packageUUID);

logger.info(
"Sending response to GET request: \"/api/datasets/{{}}/graphs/{{}}/packages/{{}}\" from \"{}\".",
datasetName,
graphURI,
packageUUID,
originURL);
return result;
}
}
Original file line number Diff line number Diff line change
@@ -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.api.dto;

import lombok.Data;

import org.rdfarchitect.api.dto.packages.PackageDTO;

@Data
public class CopyClassRequestDTO {
Comment thread
kiph-soptim marked this conversation as resolved.

String targetDatasetName;
String targetGraphURI;
PackageDTO targetPackage;
boolean copyAsAbstract;
boolean copyAttributes;
boolean copyAssociations;
}
Original file line number Diff line number Diff line change
@@ -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;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class CopyClassResponseDTO {

String uuid;
String name;
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,20 +114,11 @@ public void replaceClass(

public UUID insertClass(Graph graph, PrefixMapping prefixMapping, CIMClass newClass) {
var dataset = SessionDataStore.wrapGraphInDataset(graph, null);
var uuid = UUID.randomUUID();
var model = ModelFactory.createModelForGraph(graph);
var existingResource = model.getResource(newClass.getUri().toString());
assertNoPackageWithSameIri(graph, newClass.getUri());
if (existingResource != null && existingResource.hasProperty(RDFA.uuid)) {
var existingUUIDLiteral = existingResource.getProperty(RDFA.uuid).getObject();
if (existingUUIDLiteral != null) {
uuid = UUID.fromString(existingUUIDLiteral.asLiteral().getString());
}
}
newClass.setUuid(uuid);
newClass.setUuid(createUUID(graph, newClass));
var insertClass = insertClass(prefixMapping, null, newClass);
UpdateExecutionFactory.create(insertClass.build(), dataset).execute();
return uuid;
return newClass.getUuid();
}

private UpdateBuilder insertClass(
Expand Down Expand Up @@ -164,6 +155,70 @@ private UpdateBuilder insertClass(
return classBaseUpdate;
}

private UUID createUUID(Graph graph, CIMClass newClass) {
var uuid = UUID.randomUUID();
var model = ModelFactory.createModelForGraph(graph);
var existingResource = model.getResource(newClass.getUri().toString());
if (existingResource != null && existingResource.hasProperty(RDFA.uuid)) {
var existingUUIDLiteral = existingResource.getProperty(RDFA.uuid).getObject();
if (existingUUIDLiteral != null) {
uuid = UUID.fromString(existingUUIDLiteral.asLiteral().getString());
}
}
return uuid;
}

public UUID insertUMLAdaptedClass(
Graph graph,
PrefixMapping prefixMapping,
CIMClassUMLAdapted newClass,
boolean newValuesAsBlankNode) {

assertNoPackageWithSameIri(graph, newClass.getUri());

newClass.setUuid(createUUID(graph, newClass));

Comment thread
kiph-soptim marked this conversation as resolved.
var request = new UpdateRequest();

request.add(insertClass(prefixMapping, null, newClass).build());

if (newClass.getAttributes() != null) {
for (CIMAttribute attribute : newClass.getAttributes()) {
AttributeFixedDefaultResolver.resolve(graph, attribute, newValuesAsBlankNode);
request.add(
insertAttribute(graph, prefixMapping, null, attribute, newValuesAsBlankNode)
.build());
}
}

if (newClass.getEnumEntries() != null && !newClass.getEnumEntries().isEmpty()) {
var enumUpdate =
new CIMBaseUpdateBuilder().addPrefixes(prefixMapping).setGraph(null).build();
for (CIMEnumEntry enumEntry : newClass.getEnumEntries()) {
appendInsertEnumEntry(enumUpdate, enumEntry);
}
request.add(enumUpdate.build());
}

if (newClass.getAssociationPairs() != null && !newClass.getAssociationPairs().isEmpty()) {
var assocUpdate =
new CIMBaseUpdateBuilder()
.addPrefixes(prefixMapping)
.setGraph(null)
.build()
.addOptional("?sub", "?pre", "?obj");
for (CIMAssociationPair pair : newClass.getAssociationPairs()) {
appendInsertAssociationPair(assocUpdate, pair);
}
request.add(assocUpdate.build());
}

var dataset = SessionDataStore.wrapGraphInDataset(graph, null);
UpdateExecutionFactory.create(request, dataset).execute();

return newClass.getUuid();
}

public void deleteClass(Graph graph, PrefixMapping prefixMapping, String classUUID) {
var dataset = SessionDataStore.wrapGraphInDataset(graph, null);
UpdateExecutionFactory.create(deleteAttributes(prefixMapping, null, classUUID), dataset)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.apache.jena.shared.PrefixMapping;
import org.rdfarchitect.models.cim.data.CIMObjectFactory;
import org.rdfarchitect.models.cim.data.CIMObjectFetcher;
import org.rdfarchitect.models.cim.data.dto.CIMClass;
import org.rdfarchitect.models.cim.data.dto.relations.CIMSStereotype;
import org.rdfarchitect.models.cim.queries.CIMQueryVars;
import org.rdfarchitect.models.cim.queries.select.CIMQueries;
Expand All @@ -50,7 +51,11 @@ public static CIMClassUMLAdapted createCIMClassUMLAdapted(
Graph graph, String graphUri, PrefixMapping prefixMapping, String classUUID) {
var objectFetcher = new CIMObjectFetcher(graph, graphUri, prefixMapping);
// fetch class
var classObject = new CIMClassUMLAdapted(objectFetcher.fetchCIMClass(classUUID));
CIMClass cimClass = objectFetcher.fetchCIMClass(classUUID);
if (cimClass == null) {
return null;
}
var classObject = new CIMClassUMLAdapted(cimClass);

// if enum, then fetch enum entries
if (classObject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ public ClassUMLAdaptedDTO getClassInformation(
graphIdentifier.graphUri(),
databasePort.getPrefixMapping(graphIdentifier.datasetName()),
classUUID);
if (cimClass == null) {
return null;
}
return umlAdaptedClassMapper.toDTO(cimClass);
} finally {
if (graph != null) {
Expand Down
Loading