From 7603e60b76fc34571e664960648da4a41c5a3e51 Mon Sep 17 00:00:00 2001
From: Sirieam Marie Hunke <hunke@itc.rwth-aachen.de>
Date: Tue, 27 Aug 2024 14:00:16 +0000
Subject: [PATCH] New: Migrate SQL2Linked to GD

---
 src/GraphDeployer/Deployer.cs                 | 135 +++++++++-
 .../Implementations/PatchGraph.cs             |  77 ++++++
 .../ResourcetypeStructuralData.cs             |  93 +++++++
 .../Implementations/RoleStructuralData.cs     |  77 ++++++
 .../SQL2LinkedConfiguration.cs                |  17 ++
 src/GraphDeployer/Utils/RdfUris.cs            | 234 ++++++++++++++++++
 src/GraphDeployer/Utils/UriHelper.cs          |  52 ++++
 7 files changed, 684 insertions(+), 1 deletion(-)
 create mode 100644 src/GraphDeployer/Implementations/PatchGraph.cs
 create mode 100644 src/GraphDeployer/Implementations/ResourcetypeStructuralData.cs
 create mode 100644 src/GraphDeployer/Implementations/RoleStructuralData.cs
 create mode 100644 src/GraphDeployer/Models/ConfigurationModels/SQL2LinkedConfiguration.cs
 create mode 100644 src/GraphDeployer/Utils/RdfUris.cs
 create mode 100644 src/GraphDeployer/Utils/UriHelper.cs

diff --git a/src/GraphDeployer/Deployer.cs b/src/GraphDeployer/Deployer.cs
index 790c84c..46484df 100644
--- a/src/GraphDeployer/Deployer.cs
+++ b/src/GraphDeployer/Deployer.cs
@@ -1,4 +1,5 @@
-using Coscine.ApiClient;
+using System.Globalization;
+using Coscine.ApiClient;
 using Coscine.ApiClient.Core.Api;
 using Coscine.ApiClient.Core.Model;
 using Coscine.GraphDeployer.Models.ConfigurationModels;
@@ -8,6 +9,7 @@ using Microsoft.Extensions.Logging;
 using Microsoft.Extensions.Options;
 using Polly;
 using VDS.RDF;
+using VDS.RDF.Parsing;
 using static Coscine.GraphDeployer.Utils.CommandLineOptions;
 using Configuration = Coscine.ApiClient.Core.Client.Configuration;
 
@@ -166,6 +168,9 @@ public class Deployer
                         continue;
                     }
                 }
+                // Convert to linked data
+
+                // Store the graph
             }
 
             // Clean up the working folder
@@ -265,4 +270,132 @@ public class Deployer
             _logger.LogError(e, "Failed to delete contents of the working folder: \"{workingFolder}\"", WorkingFolder);
         }
     }
+     /// <summary>
+    /// Asynchronously stores a collection of RDF graphs in the underlying data store.
+    /// </summary>
+    /// <param name="graphs">An asynchronous enumerable of RDF graphs (<see cref="IAsyncEnumerable{IGraph}"/>) to be stored.</param>
+    /// <returns>A task representing the asynchronous operation of storing the graphs.</returns>
+    public async Task StoreGraphs(IAsyncEnumerable<IGraph> graphs)
+    {
+        var formatEnum = RdfFormat.TextTurtle;
+        var format = "text/turtle";
+
+        await foreach (var graph in graphs)
+        {
+            Console.WriteLine($" ({graph.BaseUri})");
+
+            try
+            {
+                var rdfWriter = MimeTypesHelper.GetWriter(format);
+                var content = VDS.RDF.Writing.StringWriter.Write(graph, rdfWriter);
+
+                if (graph is PatchGraph patchGraph)
+                {
+                    var patchOperations = new List<RdfPatchOperationDto>
+                    {
+                        new(RdfPatchOperationType.A, new RdfDefinitionForManipulationDto(content, formatEnum))
+                    };
+
+                    await _adminApi.PatchMetadataAsync(
+                        graph.BaseUri.AbsoluteUri,
+                        new RdfPatchDocumentDto(patchOperations)
+                    );
+                }
+                else
+                {
+                    await _adminApi.UpdateMetadataGraphAsync(
+                        graph.BaseUri.AbsoluteUri,
+                        new MetadataUpdateAdminParameters(new RdfDefinitionForManipulationDto(content, formatEnum))
+                    );
+                }
+
+                Console.WriteLine($" - Graph {graph.BaseUri} added successfully");
+                Console.WriteLine();
+            }
+            catch (Exception e)
+            {
+                Console.Error.WriteLine($"Error on ({graph.BaseUri}):");
+                Console.Error.WriteLine(e);
+                Console.Error.WriteLine(e.InnerException);
+                Console.Error.WriteLine();
+            }
+        }
+    }
+
+
+    // TODO: Move to own class
+    public void AddModifiedDate(IGraph graph, Uri rootUri)
+    {
+        AssertToGraphLiteralNode(
+            graph,
+            rootUri,
+            RdfUris.DcTermsModified,
+            DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture),
+            new Uri(XmlSpecsHelper.XmlSchemaDataTypeDateTime)
+        );
+    }
+
+    /// <summary>
+    /// Asserts a triple to the graph with a URI node object.
+    /// </summary>
+    /// <param name="graph">The graph to assert to.</param>
+    /// <param name="graphSubject">The subject URI of the triple.</param>
+    /// <param name="graphPredicate">The predicate URI of the triple.</param>
+    /// <param name="graphObject">The object URI of the triple.</param>
+    public void AssertToGraphUriNode(IGraph graph, Uri graphSubject, Uri graphPredicate, Uri? graphObject)
+    {
+        if (graphObject != null)
+        {
+            graph.Assert(
+                new Triple(
+                    graph.CreateUriNode(graphSubject),
+                    graph.CreateUriNode(graphPredicate),
+                    graph.CreateUriNode(graphObject)
+                )
+            );
+        }
+    }
+
+    /// <summary>
+    /// Asserts a triple to the graph with a literal node object.
+    /// </summary>
+    /// <param name="graph">The graph to assert to.</param>
+    /// <param name="graphSubject">The subject URI of the triple.</param>
+    /// <param name="graphPredicate">The predicate URI of the triple.</param>
+    /// <param name="graphObject">The literal object of the triple.</param>
+    /// <param name="objectType">The data type URI of the literal object, if any.</param>
+    public void AssertToGraphLiteralNode(IGraph graph, Uri graphSubject, Uri graphPredicate, string? graphObject, Uri? objectType = null)
+    {
+        if (graphObject != null)
+        {
+            graph.Assert(
+                new Triple(
+                    graph.CreateUriNode(graphSubject),
+                    graph.CreateUriNode(graphPredicate),
+                    objectType is not null ? graph.CreateLiteralNode(graphObject, objectType) : graph.CreateLiteralNode(graphObject)
+                )
+            );
+        }
+    }
+
+    /// <summary>
+    /// Asserts a triple to the graph with a blank node subject and a URI node object.
+    /// </summary>
+    /// <param name="graph">The graph to assert to.</param>
+    /// <param name="graphSubject">The blank node subject of the triple.</param>
+    /// <param name="graphPredicate">The predicate URI of the triple.</param>
+    /// <param name="graphObject">The object URI of the triple.</param>
+    public void AssertToGraphBlankAndUriNode(IGraph graph, IBlankNode graphSubject, Uri graphPredicate, Uri? graphObject)
+    {
+        if (graphObject != null)
+        {
+            graph.Assert(
+                new Triple(
+                    graphSubject,
+                    graph.CreateUriNode(graphPredicate),
+                    graph.CreateUriNode(graphObject)
+                )
+            );
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/GraphDeployer/Implementations/PatchGraph.cs b/src/GraphDeployer/Implementations/PatchGraph.cs
new file mode 100644
index 0000000..ff4ad94
--- /dev/null
+++ b/src/GraphDeployer/Implementations/PatchGraph.cs
@@ -0,0 +1,77 @@
+using VDS.RDF;
+
+namespace Coscine.GraphDeployer;
+
+/// <summary>
+/// Represents a specialized RDF graph for small-scale updates.
+/// This class extends the standard Graph class with additional
+/// functionalities for tracking changes (assertions and retractions).
+/// </summary>
+/// <remarks><i>TODO: Consider extending <see cref="ITransactionalGraph"/> to allow for rollback and commit operations.</i></remarks>
+public class PatchGraph : Graph
+{
+    public List<Triple> AssertList { get; set; } = new();
+
+    public List<Triple> RetractList { get; set; } = new();
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="PatchGraph"/> class.
+    /// </summary>
+    public PatchGraph() : base()
+    {
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="PatchGraph"/> class.
+    /// </summary>
+    public PatchGraph(Uri graphUri) : base(graphUri)
+    {
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="IPatchGraph"/> class with the specified graph URI.
+    /// </summary>
+    /// <param name="graphUri">The base URI for the graph.</param>
+    /// <returns>A new empty instance of <see cref="IPatchGraph"/> with the provided base URI.</returns>
+    public static PatchGraph Empty(Uri graphUri)
+    {
+        return new PatchGraph(graphUri)
+        {
+            BaseUri = graphUri
+        };
+    }
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="IPatchGraph"/> class with the specified graph URI.
+    /// </summary>
+    /// <param name="graphUri">The base URI for the graph as a string.</param>
+    /// <returns>A new empty instance of <see cref="IPatchGraph"/> with the provided base URI.</returns>
+    public static PatchGraph Empty(string graphUri)
+    {
+        return Empty(new Uri(graphUri));
+    }
+
+    public override bool Assert(Triple t)
+    {
+        AssertList.Add(t);
+        return base.Assert(t);
+    }
+
+    public override bool Assert(IEnumerable<Triple> triples)
+    {
+        AssertList.AddRange(triples);
+        return base.Assert(triples);
+    }
+
+    public override bool Retract(Triple t)
+    {
+        RetractList.Add(t);
+        return base.Retract(t);
+    }
+
+    public override bool Retract(IEnumerable<Triple> triples)
+    {
+        RetractList.AddRange(triples);
+        return base.Retract(triples);
+    }
+}
diff --git a/src/GraphDeployer/Implementations/ResourcetypeStructuralData.cs b/src/GraphDeployer/Implementations/ResourcetypeStructuralData.cs
new file mode 100644
index 0000000..3b5356d
--- /dev/null
+++ b/src/GraphDeployer/Implementations/ResourcetypeStructuralData.cs
@@ -0,0 +1,93 @@
+using Coscine.ApiClient.Core.Api;
+using Coscine.ApiClient.Core.Model;
+using VDS.RDF;
+using VDS.RDF.Parsing;
+
+namespace Coscine.GraphDeployer;
+/// <summary>
+/// Class responsible for converting resource type data into linked data graphs.
+/// It retrieves resource type information from the API and then transforms this data into a series of RDF graphs,
+/// making use of predefined URIs and RDF constructs.
+/// </summary>
+public class ResourceTypeStructuralData
+{
+    private readonly Deployer _deployer;
+    private readonly AdminApi _adminApi;
+    public ResourceTypeStructuralData(AdminApi adminApi, Deployer deployer)
+    {
+        _adminApi = adminApi;
+        _deployer = deployer;
+
+    }
+    public async IAsyncEnumerable<ResourceTypeInformationDto> GetAll()
+    {
+        var resourceTypeApi = new ResourceTypeApi(apiClientConfig);
+        var resourceTypeInformationsResponse = await resourceTypeApi.GetAllResourceTypesInformationAsync();
+        foreach (var item in resourceTypeInformationsResponse.Data)
+        {
+            yield return item; // Yield each item in the list to keep the IAsyncEnumerable logic
+        }
+    }
+
+    public override async IAsyncEnumerable<IGraph> ConvertToLinkedDataAsync(IAsyncEnumerable<ResourceTypeInformationDto> entries)
+    {
+        await foreach (var entry in entries)
+        {
+            var resourceTypeGraphName = UriHelper.TryCombineUri(RdfUris.CoscineResourceTypes, entry.Id)
+                ?? throw new Exception("Could not combine resource types prefix with resource type ID");
+            var response = _adminApi.GetMetadataGraph(resourceTypeGraphName.AbsoluteUri, RdfFormat.TextTurtle);
+
+            var graph = new Graph()
+            {
+                BaseUri = resourceTypeGraphName
+            };
+
+            graph.LoadFromString(response.Data.Content, new TurtleParser());
+
+            // check if a triple with a dcat:DataService already exists in the resourcetype graph
+            var getTriplesDcatDataService = graph.GetTriplesWithObject(RdfUris.DcatDataServiceClass);
+
+            if (!getTriplesDcatDataService.Any())
+            {
+                _deployer.AssertToGraphUriNode(graph, resourceTypeGraphName, RdfUris.A, RdfUris.DcatDataServiceClass);
+                Console.WriteLine($"For resource type '{entry.SpecificType}' will migrate triple '{graph.BaseUri} {RdfUris.A} {RdfUris.DcatDataServiceClass}'. ");
+            }
+            else
+            {
+                Console.WriteLine($"For resource type '{entry.SpecificType}' will NOT migrate triple '{graph.BaseUri} {RdfUris.A} {RdfUris.DcatDataServiceClass}'. ");
+            }
+
+            // check if a triple with dcterms:title '{entry.DisplayName}' already exists in the role graph
+            var getTriplesDctermsTitle = graph.GetTriplesWithPredicate(RdfUris.DcTermsTitle);
+
+            if (!getTriplesDctermsTitle.Any())
+            {
+                _deployer.AssertToGraphLiteralNode(graph, resourceTypeGraphName, RdfUris.DcTermsTitle, entry.SpecificType);
+                Console.WriteLine($"For resource type '{entry.SpecificType}' will migrate triple '{graph.BaseUri} {RdfUris.DcTermsTitle} {entry.SpecificType}'. ");
+            }
+            else
+            {
+                Console.WriteLine($"For resource type '{entry.SpecificType}' will NOT migrate triple '{graph.BaseUri} {RdfUris.DcTermsTitle} {entry.SpecificType}'. ");
+            }
+
+
+            // check if a triple with dcterms:title '{entry.DisplayName}' already exists in the role graph
+            var getTriplesDctermsAlternative = graph.GetTriplesWithPredicate(RdfUris.DcTermsAlternative);
+
+            if (!getTriplesDctermsAlternative.Any())
+            {
+                _deployer.AssertToGraphLiteralNode(graph, resourceTypeGraphName, RdfUris.DcTermsAlternative, entry.GeneralType);
+                Console.WriteLine($"For resource type '{entry.SpecificType}' will migrate triple '{graph.BaseUri} {RdfUris.DcTermsAlternative} {entry.GeneralType}'. ");
+            }
+            else
+            {
+                Console.WriteLine($"For resource type '{entry.SpecificType}' will NOT migrate triple '{graph.BaseUri} {RdfUris.DcTermsAlternative} {entry.GeneralType}'. ");
+            }
+
+            if (!getTriplesDcatDataService.Any() || !getTriplesDctermsTitle.Any())
+            {
+                yield return graph;
+            }
+        }
+    }
+}
diff --git a/src/GraphDeployer/Implementations/RoleStructuralData.cs b/src/GraphDeployer/Implementations/RoleStructuralData.cs
new file mode 100644
index 0000000..a9e2734
--- /dev/null
+++ b/src/GraphDeployer/Implementations/RoleStructuralData.cs
@@ -0,0 +1,77 @@
+using Coscine.ApiClient;
+using Coscine.ApiClient.Core.Api;
+using Coscine.ApiClient.Core.Model;
+using VDS.RDF;
+using VDS.RDF.Parsing;
+
+namespace Coscine.GraphDeployer;
+
+/// <summary>
+/// Class responsible for converting role data into linked data graphs.
+/// It retrieves role information from the API and then transforms this data into a series of RDF graphs,
+/// making use of predefined URIs and RDF constructs.
+/// </summary>
+public class RoleStructuralData
+{
+    private readonly AdminApi _adminApi;
+    private readonly Deployer _deployer;
+    public RoleStructuralData(AdminApi adminApi, Deployer deployer)
+    {
+        _adminApi = adminApi;
+        _deployer = deployer;
+
+    }
+    public IAsyncEnumerable<RoleDto> GetAll()
+    {
+        var roleApi = new RoleApi(apiClientConfig);
+        return PaginationHelper.GetAllAsync<RoleDtoPagedResponse, RoleDto>(
+            (currentPage) => roleApi.GetRolesAsync(pageNumber: currentPage));
+    }
+    public async IAsyncEnumerable<IGraph> ConvertToLinkedDataAsync(IAsyncEnumerable<RoleDto> entries)
+    {
+        await foreach (var entry in entries)
+        {
+            var roleGraphName = UriHelper.TryCombineUri(RdfUris.CoscineRoles, entry.Id)
+                ?? throw new Exception("Could not combine role prefix with role ID");
+            var response = await _adminApi.GetMetadataGraphAsync(roleGraphName.AbsoluteUri, RdfFormat.TextTurtle);
+
+            var graph = new Graph()
+            {
+                BaseUri = roleGraphName
+            };
+
+            graph.LoadFromString(response.Data.Content, new TurtleParser());
+
+            // check if a triple with a org:role already exists in the role graph
+            var getTriplesOrgRole = graph.GetTriplesWithObject(RdfUris.OrgRoleClass);
+
+            if (!getTriplesOrgRole.Any())
+            {
+                _deployer.AssertToGraphUriNode(graph, roleGraphName, RdfUris.A, RdfUris.OrgRoleClass);
+                Console.WriteLine($"For role '{entry.DisplayName}' will migrate triple '{graph.BaseUri} {RdfUris.A} {RdfUris.OrgRoleClass}'. ");
+            }
+            else
+            {
+                Console.WriteLine($"For role '{entry.DisplayName}' will NOT migrate triple '{graph.BaseUri} {RdfUris.A} {RdfUris.OrgRoleClass}'. ");
+            }
+
+            // check if a triple with dcterms:title '{entry.DisplayName}' already exists in the role graph
+            var getTriplesDctermsTitle = graph.GetTriplesWithPredicate(RdfUris.DcTermsTitle);
+
+            if (!getTriplesDctermsTitle.Any())
+            {
+                _deployer.AssertToGraphLiteralNode(graph, roleGraphName, RdfUris.DcTermsTitle, entry.DisplayName);
+                Console.WriteLine($"For role '{entry.DisplayName}' will migrate triple '{graph.BaseUri} {RdfUris.DcTermsTitle} {entry.DisplayName}'. ");
+            }
+            else
+            {
+                Console.WriteLine($"For role '{entry.DisplayName}' will NOT migrate triple '{graph.BaseUri} {RdfUris.DcTermsTitle} {entry.DisplayName}'. ");
+            }
+            if (!getTriplesOrgRole.Any() || !getTriplesDctermsTitle.Any())
+            {
+                yield return graph;
+            }
+        }
+    }
+
+}
diff --git a/src/GraphDeployer/Models/ConfigurationModels/SQL2LinkedConfiguration.cs b/src/GraphDeployer/Models/ConfigurationModels/SQL2LinkedConfiguration.cs
new file mode 100644
index 0000000..a7e30c6
--- /dev/null
+++ b/src/GraphDeployer/Models/ConfigurationModels/SQL2LinkedConfiguration.cs
@@ -0,0 +1,17 @@
+namespace Coscine.GraphDeployer.Models.ConfigurationModels;
+
+/// <summary>
+/// Represents the configuration settings used in the application.
+/// </summary>
+public class SQL2LinkedConfiguration
+{
+    /// <summary>
+    /// The section name in the configuration file.
+    /// </summary>
+    public static readonly string Section = "SQL2LinkedConfiguration";
+
+    /// <summary>
+    /// Value indicating whether SQL2Linked is enabled.
+    /// </summary>
+    public bool IsEnabled { get; init; }
+}
diff --git a/src/GraphDeployer/Utils/RdfUris.cs b/src/GraphDeployer/Utils/RdfUris.cs
new file mode 100644
index 0000000..2eedac6
--- /dev/null
+++ b/src/GraphDeployer/Utils/RdfUris.cs
@@ -0,0 +1,234 @@
+// Ignore Spelling: Foaf Dcat Dcterms Ebocore Fdp Ldp Xsd
+
+/// ------------------
+/// TAKEN FROM THE API
+/// ------------------
+
+namespace Coscine.GraphDeployer;
+
+/// <summary>
+/// Contains URIs relevant to RDF (Resource Description Framework) and related schemas/ontologies.
+/// </summary>
+public static class RdfUris
+{
+    // ACL
+    public static readonly Uri AclPrefix = new("http://www.w3.org/ns/auth/acl#");
+
+    public static readonly Uri AclAccessTo = new("http://www.w3.org/ns/auth/acl#accessTo");
+    public static readonly Uri AclAgentGroup = new("http://www.w3.org/ns/auth/acl#agentGroup");
+    public static readonly Uri AclDefault = new("http://www.w3.org/ns/auth/acl#default");
+    public static readonly Uri AclMode = new("http://www.w3.org/ns/auth/acl#mode");
+
+    public static readonly Uri AclAuthorizationClass = new("http://www.w3.org/ns/auth/acl#Authorization");
+    public static readonly Uri AclReadClass = new("http://www.w3.org/ns/auth/acl#Read");
+    public static readonly Uri AclWriteClass = new("http://www.w3.org/ns/auth/acl#Write");
+
+    // Coscine
+    public static readonly Uri CoscinePrefix = new("https://purl.org/coscine/");
+
+    public static readonly Uri CoscineApplicationProfile = new("https://purl.org/coscine/ap/");
+    public static readonly Uri CoscineFixedValue = new("https://purl.org/coscine/fixedValue");
+    public static readonly Uri CoscineMetadataExtractionEntity = new("https://purl.org/coscine/terms/metatadataextraction#Entity");
+    public static readonly Uri CoscineMetadataExtractionExtracted = new("https://purl.org/coscine/terms/metatadataextraction#extracted");
+    public static readonly Uri CoscineMetadataExtractionVersion = new("https://purl.org/coscine/terms/metatadataextraction#version");
+    public static readonly Uri CoscineMetadataTrackerIsVariantOf = new("https://purl.org/coscine/terms/metadatatracker#isVariantOf");
+    public static readonly Uri CoscineMetadataTrackerSimilarity = new("https://purl.org/coscine/terms/metadatatracker#similarity");
+    public static readonly Uri CoscineMetadataTrackerSimilarityToLastVersion = new("https://purl.org/coscine/terms/metadatatracker#similarityToLastVersion");
+    public static readonly Uri CoscineMetadataTrackerHasVariant = new("https://purl.org/coscine/terms/metadatatracker#hasVariant");
+    public static readonly Uri CoscineMetadataTrackerVariant = new("https://purl.org/coscine/terms/metadatatracker#Variant");
+    public static readonly Uri CoscineProjects = new("https://purl.org/coscine/projects/");
+    public static readonly Uri CoscineResourceTypes = new("https://purl.org/coscine/resourcetypes/");
+    public static readonly Uri CoscineResources = new("https://purl.org/coscine/resources/");
+    public static readonly Uri CoscineRoles = new("https://purl.org/coscine/roles/");
+    public static readonly Uri CoscineUsers = new("https://purl.org/coscine/users/");
+
+    [Obsolete($"Beware the side-effects before changing! Use {nameof(CoscineUsers)} instead.")]
+    public static readonly Uri CoscineUserLegacy = new("https://coscine.rwth-aachen.de/u/");
+
+    // Coscine Entities
+    public static readonly Uri CoscineEntitiesGraph = new("https://purl.org/coscine/entities/graph#");
+    public static readonly Uri CoscineEntitiesGraphDeployed = new("https://purl.org/coscine/entities/graph#deployed");
+
+    // Coscine Terms
+    public static readonly Uri CoscineTermsPrefix = new("https://purl.org/coscine/terms/");
+
+    public static readonly Uri CoscineTermsLinkedBody = new("https://purl.org/coscine/terms/linked#body");
+    public static readonly Uri CoscineTermsMetadataTrackerAgent = new("https://purl.org/coscine/terms/metadatatracker#Agent");
+    public static readonly Uri CoscineTermsProject = new("https://purl.org/coscine/terms/project#");
+    public static readonly Uri CoscineTermsProjectDeleted = new("https://purl.org/coscine/terms/project#deleted");
+    public static readonly Uri CoscineTermsProjectSlug = new("https://purl.org/coscine/terms/project#slug");
+    public static readonly Uri CoscineTermsProjectVisibility = new("https://purl.org/coscine/terms/project#visibility");
+    public static readonly Uri CoscineTermsResource = new("https://purl.org/coscine/terms/resource#");
+    public static readonly Uri CoscineTermsResourceArchived = new("https://purl.org/coscine/terms/resource#archived");
+    public static readonly Uri CoscineTermsResourceDeleted = new("https://purl.org/coscine/terms/resource#deleted");
+    public static readonly Uri CoscineTermsResourceFixedValues = new("https://purl.org/coscine/terms/resource#fixedValues");
+    public static readonly Uri CoscineTermsResourceVisibility = new("https://purl.org/coscine/terms/resource#visibility");
+    public static readonly Uri CoscineTermsTypes = new("https://purl.org/coscine/terms/types#");
+    public static readonly Uri CoscineTermsMetadataExtractionVersion = new("https://purl.org/coscine/terms/metatadataextraction#version");
+    public static readonly Uri CoscineTermsUserAgent = new("https://purl.org/coscine/terms/user#Agent");
+    public static readonly Uri CoscineTermsVisibility = new("https://purl.org/coscine/terms/visibility#");
+    public static readonly Uri CoscineTermsVisibilityPublic = new("https://purl.org/coscine/terms/visibility#public");
+    public static readonly Uri CoscineTermsVisibilityProjectMember = new("https://purl.org/coscine/terms/visibility#projectMember");
+    public static readonly Uri CoscineTermsRole = new("https://purl.org/coscine/terms/role#");
+    public static readonly Uri CoscineTermsOrganizations = new("https://purl.org/coscine/terms/organization#");
+    public static readonly Uri CoscineTermsDeployedVersion = new("https://purl.org/coscine/terms/deployed#version");
+
+    // CSMD
+    public static readonly Uri CsmdPrefix = new("http://www.purl.org/net/CSMD/4.0#");
+
+    // DCAT
+    public static readonly Uri DcatPrefix = new("http://www.w3.org/ns/dcat#");
+
+    public static readonly Uri DcatCatalog = new("http://www.w3.org/ns/dcat#catalog");
+    public static readonly Uri DcatDataset = new("http://www.w3.org/ns/dcat#dataset");
+    public static readonly Uri DcatDistribution = new("http://www.w3.org/ns/dcat#distribution");
+    public static readonly Uri DcatService = new("http://www.w3.org/ns/dcat#service");
+
+    public static readonly Uri DcatCatalogClass = new("http://www.w3.org/ns/dcat#Catalog");
+    public static readonly Uri DcatDataServiceClass = new("http://www.w3.org/ns/dcat#DataService");
+    public static readonly Uri DcatDatasetClass = new("http://www.w3.org/ns/dcat#Dataset");
+
+    // DCMI Type
+    public static readonly Uri DcmiTypePrefix = new("http://purl.org/dc/dcmitype/");
+
+    // DC
+    public static readonly Uri DcPrefix = new("http://purl.org/dc/elements/1.1/");
+
+    // DC Terms
+    public static readonly Uri DcTermsPrefix = new("http://purl.org/dc/terms/");
+
+    public static readonly Uri DcTermsAlternative = new("http://purl.org/dc/terms/alternative");
+    public static readonly Uri DcTermsConformsTo = new("http://purl.org/dc/terms/conformsTo");
+    public static readonly Uri DcTermsCreator = new("http://purl.org/dc/terms/creator");
+    public static readonly Uri DcTermsCreated = new("http://purl.org/dc/terms/created");
+    public static readonly Uri DcTermsDescription = new("http://purl.org/dc/terms/description");
+    public static readonly Uri DcTermsEndDate = new("http://purl.org/dc/terms/endDate");
+    public static readonly Uri DcTermsIdentifier = new("http://purl.org/dc/terms/identifier");
+    public static readonly Uri DcTermsIsPartOf = new("http://purl.org/dc/terms/isPartOf");
+    public static readonly Uri DcTermsLicense = new("http://purl.org/dc/terms/license");
+    public static readonly Uri DcTermsModified = new("http://purl.org/dc/terms/modified");
+    public static readonly Uri DcTermsRights = new("http://purl.org/dc/terms/rights");
+    public static readonly Uri DcTermsRightsHolder = new("http://purl.org/dc/terms/rightsHolder");
+    public static readonly Uri DcTermsStartDate = new("http://purl.org/dc/terms/startDate");
+    public static readonly Uri DcTermsSubject = new("http://purl.org/dc/terms/subject");
+    public static readonly Uri DcTermsTitle = new("http://purl.org/dc/terms/title");
+
+    // EBUCORE
+    public static readonly Uri EbocorePrefix = new("http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#");
+
+    public static readonly Uri EbocoreHash = new("http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#hash");
+    public static readonly Uri EbocoreHashFunction = new("http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#hashFunction");
+    public static readonly Uri EbocoreHashType = new("http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#hashType");
+    public static readonly Uri EbocoreHashValue = new("http://www.ebu.ch/metadata/ontologies/ebucore/ebucore#hashValue");
+
+    // FDP
+    public static readonly Uri FdpPrefix = new("http://purl.org/fdp/fdp-o#");
+
+    public static readonly Uri FdpHasMetadata = new("http://purl.org/fdp/fdp-o#hasMetadata");
+    public static readonly Uri FdpIsMetadataOf = new("http://purl.org/fdp/fdp-o#isMetadataOf");
+
+    public static readonly Uri FdpMetadataServiceClass = new("http://purl.org/fdp/fdp-o#MetadataService");
+
+    // FOAF
+    public static readonly Uri FoafPrefix = new("http://xmlns.com/foaf/0.1/");
+
+    public static readonly Uri FoafHomepage = new("http://xmlns.com/foaf/0.1/homepage");
+    public static readonly Uri FoafName = new("http://xmlns.com/foaf/0.1/name");
+
+    public static readonly Uri FoafPersonClass = new("http://xmlns.com/foaf/0.1/Person");
+
+    // Handle
+    public static readonly Uri HandlePrefix = new("https://hdl.handle.net/");
+
+    // LDP
+    public static readonly Uri LdpPrefix = new("http://www.w3.org/ns/ldp#");
+
+    public static readonly Uri LdpDescribedBy = new("http://www.w3.org/ns/ldp#describedBy");
+
+    public static readonly Uri LdpBasicContainerClass = new("http://www.w3.org/ns/ldp#BasicContainer");
+    public static readonly Uri LdpNonRDFSourceClass = new("http://www.w3.org/ns/ldp#NonRDFSource");
+    public static readonly Uri LdpRDFSourceClass = new("http://www.w3.org/ns/ldp#RDFSource");
+
+    // ORG
+    public static readonly Uri OrgPrefix = new("http://www.w3.org/ns/org#");
+
+    public static readonly Uri OrgMember = new("http://www.w3.org/ns/org#member");
+    public static readonly Uri OrgOrganization = new("http://www.w3.org/ns/org#organization");
+    public static readonly Uri OrgRole = new("http://www.w3.org/ns/org#role");
+
+    public static readonly Uri OrgMembershipClass = new("http://www.w3.org/ns/org#Membership");
+    public static readonly Uri OrgOrganizationalCollaborationClass = new("http://www.w3.org/ns/org#OrganizationalCollaboration");
+    public static readonly Uri OrgRoleClass = new("http://www.w3.org/ns/org#Role");
+
+    // OWL
+    public static readonly Uri OwlPrefix = new("http://www.w3.org/2002/07/owl#");
+
+    public static readonly Uri OwlImports = new("http://www.w3.org/2002/07/owl#imports");
+
+    // PIM
+    public static readonly Uri PimPrefix = new("http://www.w3.org/ns/pim/space#");
+
+    public static readonly Uri PimStorageClass = new("http://www.w3.org/ns/pim/space#Storage");
+
+    // PROV
+    public static readonly Uri ProvPrefix = new("http://www.w3.org/ns/prov#");
+
+    public static readonly Uri ProvEntityClass = new("http://www.w3.org/ns/prov#Entity");
+
+    public static readonly Uri ProvGeneratedAtTime = new("http://www.w3.org/ns/prov#generatedAtTime");
+    public static readonly Uri ProvWasInvalidatedBy = new("http://www.w3.org/ns/prov#wasInvalidatedBy");
+    public static readonly Uri ProvWasRevisionOf = new("http://www.w3.org/ns/prov#wasRevisionOf");
+
+    // RDF
+    public static readonly Uri RdfUrlPrefix = new("http://www.w3.org/1999/02/22-rdf-syntax-ns#");
+
+    public static readonly Uri A = new("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
+
+    // RDFS
+    public static readonly Uri RdfsUrlPrefix = new("http://www.w3.org/2000/01/rdf-schema#");
+
+    public static readonly Uri SubClassOf = new("http://www.w3.org/2000/01/rdf-schema#subClassOf");
+
+    // ROR IDs
+    /// <summary>
+    /// Represents the RWTH Aachen's ROR (Research Organization Registry) ID.
+    /// </summary>
+    public static readonly Uri RwthRorId = new("https://ror.org/04xfq0f34");
+
+    // SCHEMA
+    public static readonly Uri SchemaPrefix = new("http://schema.org/");
+
+    public static readonly Uri SchemaFunding = new("http://schema.org/funding");
+
+    // SHACL
+    public static readonly Uri ShaclPrefix = new("http://www.w3.org/ns/shacl#");
+
+    public static readonly Uri ShaclDatatype = new("http://www.w3.org/ns/shacl#datatype");
+    public static readonly Uri ShaclDeactivated = new("http://www.w3.org/ns/shacl#deactivated");
+    public static readonly Uri ShaclPath = new("http://www.w3.org/ns/shacl#path");
+    public static readonly Uri ShaclProperty = new("http://www.w3.org/ns/shacl#property");
+    public static readonly Uri ShaclTargetClass = new("http://www.w3.org/ns/shacl#targetClass");
+    public static readonly Uri Class = new("http://www.w3.org/ns/shacl#class");
+
+    // Trellis
+    public static readonly Uri TrellisPrefix = new("http://www.trellisldp.org/ns/trellis#");
+
+    public static readonly Uri TrellisGraph = new("http://www.trellisldp.org/ns/trellis#PreferServerManaged");
+
+    // Vcard
+    public static readonly Uri VcardPrefix = new("http://www.w3.org/2006/vcard/ns#");
+
+    public static readonly Uri VcardHasMember = new("http://www.w3.org/2006/vcard/ns#hasMember");
+
+    public static readonly Uri VcardGroupClass = new("http://www.w3.org/2006/vcard/ns#Group");
+
+    // XSD
+    public static readonly Uri XsdPrefix = new("http://www.w3.org/2001/XMLSchema#");
+
+    public static readonly Uri XsdDateTime = new("http://www.w3.org/2001/XMLSchema#dateTime");
+    public static readonly Uri XsdDecimal = new("http://www.w3.org/2001/XMLSchema#decimal");
+    public static readonly Uri XsdDouble = new("http://www.w3.org/2001/XMLSchema#double");
+    public static readonly Uri XsdDuration = new("http://www.w3.org/2001/XMLSchema#duration");
+    public static readonly Uri XsdBoolean = new("http://www.w3.org/2001/XMLSchema#boolean");
+    public static readonly Uri XsdHexBinary = new("http://www.w3.org/2001/XMLSchema#hexBinary");
+}
\ No newline at end of file
diff --git a/src/GraphDeployer/Utils/UriHelper.cs b/src/GraphDeployer/Utils/UriHelper.cs
new file mode 100644
index 0000000..3c4bc52
--- /dev/null
+++ b/src/GraphDeployer/Utils/UriHelper.cs
@@ -0,0 +1,52 @@
+namespace Coscine.GraphDeployer;
+
+public class UriHelper
+{
+    /// <summary>
+    ///
+    /// </summary>
+    /// <param name="baseUri"></param>
+    /// <param name="relativePath"></param>
+    /// <returns></returns>
+    public static Uri? TryCombinePath(Uri baseUri, string relativePath)
+    {
+        Uri.TryCreate(baseUri, relativePath + "/", out Uri? result);
+        return result;
+    }
+
+    /// <summary>
+    ///
+    /// </summary>
+    /// <param name="baseUri"></param>
+    /// <param name="relativePath"></param>
+    /// <returns></returns>
+    public static Uri? TryCombinePath(Uri baseUri, Guid relativePath)
+    {
+        Uri.TryCreate(baseUri, relativePath.ToString() + "/", out Uri? result);
+        return result;
+    }
+
+    /// <summary>
+    ///
+    /// </summary>
+    /// <param name="baseUri"></param>
+    /// <param name="relativePath"></param>
+    /// <returns></returns>
+    public static Uri? TryCombineUri(Uri baseUri, string relativePath)
+    {
+        Uri.TryCreate(baseUri, relativePath, out Uri? result);
+        return result;
+    }
+
+    /// <summary>
+    ///
+    /// </summary>
+    /// <param name="baseUri"></param>
+    /// <param name="relativePath"></param>
+    /// <returns></returns>
+    public static Uri? TryCombineUri(Uri baseUri, Guid relativePath)
+    {
+        Uri.TryCreate(baseUri, relativePath.ToString(), out Uri? result);
+        return result;
+    }
+}
\ No newline at end of file
-- 
GitLab