diff --git a/src/SQL2Linked/Implementations/ProjectStructuralData.cs b/src/SQL2Linked/Implementations/ProjectStructuralData.cs index f3fb09a8251b1ef97b85fc36f4884c19b936e108..23015ee5d5a1308e00fcc086eef95edf11283dca 100644 --- a/src/SQL2Linked/Implementations/ProjectStructuralData.cs +++ b/src/SQL2Linked/Implementations/ProjectStructuralData.cs @@ -28,7 +28,9 @@ public class ProjectStructuralData : StructuralData<ProjectAdminDto> AssertToGraphUriNode(coscineGraph, RdfUris.CoscinePrefix, RdfUris.DcatCatalog, RdfUris.CoscineProjects); AssertToGraphUriNode(coscineGraph, RdfUris.CoscinePrefix, RdfUris.DcatCatalog, RdfUris.CoscineResources); - yield return coscineGraph; // yeld coscineGraph first + yield return coscineGraph; // yield coscineGraph first + + var trellisGraph = PatchGraph.Empty(RdfUris.TrellisGraph); await foreach (var entry in entries) { @@ -138,14 +140,42 @@ public class ProjectStructuralData : StructuralData<ProjectAdminDto> Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.DcTermsCreator} {UriHelper.TryCombineUri(RdfUris.CoscineUsers, entry.Creator.Id)}'. "); } - if (entry.CreationDate is not null && entry.CreationDate.HasValue) { AssertToGraphLiteralNode(graph, projectGraphName, RdfUris.DcTermsCreated, entry.CreationDate.Value.ToString(), RdfUris.XsdDateTime); Console.WriteLine($"For project '{entry.DisplayName}' will migrate triple '{projectGraphName} {RdfUris.DcTermsCreated} {entry.CreationDate}'. "); } + // Add data to the trellis graph + AssertToGraphUriNode(trellisGraph, + projectGraphName, + RdfUris.A, + RdfUris.LdpBasicContainerClass); + AddModifiedDate(trellisGraph, projectGraphName); + + AssertToGraphUriNode(trellisGraph, + projectGraphName, + RdfUris.DcTermsIsPartOf, + RdfUris.CoscineProjectsEntity); + AssertToGraphUriNode(trellisGraph, + RdfUris.CoscineProjectsEntity, + RdfUris.A, + RdfUris.LdpBasicContainerClass); + AddModifiedDate(trellisGraph, RdfUris.CoscineProjectsEntity); + + AssertToGraphUriNode(trellisGraph, + RdfUris.CoscineProjectsEntity, + RdfUris.DcTermsIsPartOf, + RdfUris.CoscinePrefix); + AssertToGraphUriNode(trellisGraph, + RdfUris.CoscinePrefix, + RdfUris.A, + RdfUris.LdpBasicContainerClass); + AddModifiedDate(trellisGraph, RdfUris.CoscinePrefix); + yield return graph; } + + yield return trellisGraph; } } \ No newline at end of file diff --git a/src/SQL2Linked/Implementations/ResourceStructuralData.cs b/src/SQL2Linked/Implementations/ResourceStructuralData.cs index db6c249e65df141a462ae8e102decd2087187f95..e4c59e1302a460b8d6e4517f7130c3cfa35d8030 100644 --- a/src/SQL2Linked/Implementations/ResourceStructuralData.cs +++ b/src/SQL2Linked/Implementations/ResourceStructuralData.cs @@ -23,6 +23,8 @@ public class ResourceStructuralData : StructuralData<ResourceAdminDto> var coscineHandlePrefix = UriHelper.TryCombinePath(RdfUris.HandlePrefix, _pidConfiguration.Prefix) ?? throw new Exception("Could not combine handle prefix with PID prefix"); + var trellisGraph = PatchGraph.Empty(RdfUris.TrellisGraph); + await foreach (var entry in entries) { var resourceGraphName = UriHelper.TryCombineUri(RdfUris.CoscineResources, entry.Id) @@ -165,7 +167,57 @@ public class ResourceStructuralData : StructuralData<ResourceAdminDto> Console.WriteLine($"For resource '{entry.DisplayName}' will migrate triple '{resourceGraphName} {RdfUris.DcTermsCreated} {entry.DateCreated}'. "); } + // Add data to the trellis graph + AssertToGraphUriNode(trellisGraph, + resourceGraphName, + RdfUris.A, + RdfUris.LdpBasicContainerClass); + AddModifiedDate(trellisGraph, resourceGraphName); + + AssertToGraphUriNode(trellisGraph, + resourceGraphName, + RdfUris.DcTermsIsPartOf, + RdfUris.CoscineResourcesEntity); + AssertToGraphUriNode(trellisGraph, + RdfUris.CoscineResourcesEntity, + RdfUris.A, + RdfUris.LdpBasicContainerClass); + AddModifiedDate(trellisGraph, RdfUris.CoscineResourcesEntity); + + AssertToGraphUriNode(trellisGraph, + RdfUris.CoscineResourcesEntity, + RdfUris.DcTermsIsPartOf, + RdfUris.CoscinePrefix); + AssertToGraphUriNode(trellisGraph, + RdfUris.CoscinePrefix, + RdfUris.A, + RdfUris.LdpBasicContainerClass); + AddModifiedDate(trellisGraph, RdfUris.CoscinePrefix); + + var resourceACLGraphName = UriHelper.TryCombineUri(resourceGraphName, "?ext=acl"); + var aclGraph = new Graph() + { + BaseUri = resourceACLGraphName + }; + + var aclSubjects = graph.GetTriplesWithObject(RdfUris.AclAuthorizationClass).Select((triple) => triple.Subject); + foreach (var subject in aclSubjects) + { + var subjectNode = aclGraph.CreateBlankNode(); + foreach (var triple in graph.GetTriplesWithSubject(subject)) + { + aclGraph.Assert(new Triple( + subjectNode, + triple.Predicate, + triple.Object + )); + } + } + yield return graph; + yield return aclGraph; } + + yield return trellisGraph; } } diff --git a/src/SQL2Linked/StructuralData.cs b/src/SQL2Linked/StructuralData.cs index 20b9fcced0abeb940048262f403c00b23849bac7..6b2fdfc11cdde94c6d75eb64eae5a94a25e701a4 100644 --- a/src/SQL2Linked/StructuralData.cs +++ b/src/SQL2Linked/StructuralData.cs @@ -4,7 +4,10 @@ using Coscine.ApiClient.Core.Client; using Coscine.ApiClient.Core.Model; using Microsoft.Extensions.Configuration; using SQL2Linked.Models.ConfigurationModels; +using SQL2Linked.Utils; +using System.Globalization; using VDS.RDF; +using VDS.RDF.Parsing; namespace SQL2Linked; @@ -97,10 +100,25 @@ public abstract class StructuralData<S> var rdfWriter = MimeTypesHelper.GetWriter(format); var content = VDS.RDF.Writing.StringWriter.Write(graph, rdfWriter); - await _adminApi.UpdateMetadataGraphAsync( - graph.BaseUri.AbsoluteUri, - new MetadataUpdateAdminParameters(new RdfDefinitionForManipulationDto(content, formatEnum)) - ); + 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(); @@ -115,6 +133,17 @@ public abstract class StructuralData<S> } } + 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> diff --git a/src/SQL2Linked/Utils/PatchGraph.cs b/src/SQL2Linked/Utils/PatchGraph.cs new file mode 100644 index 0000000000000000000000000000000000000000..7d735dd5ce7e40b997439ef059099c035d838e36 --- /dev/null +++ b/src/SQL2Linked/Utils/PatchGraph.cs @@ -0,0 +1,77 @@ +using VDS.RDF; + +namespace SQL2Linked.Utils; + +/// <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); + } +} \ No newline at end of file diff --git a/src/SQL2Linked/Utils/RdfUris.cs b/src/SQL2Linked/Utils/RdfUris.cs index e5b1dd414848ca640f4b0f1fa0bce9182e4ab9e5..6d386093074c7dbc4364fabaa3b4afe112377fb2 100644 --- a/src/SQL2Linked/Utils/RdfUris.cs +++ b/src/SQL2Linked/Utils/RdfUris.cs @@ -29,8 +29,10 @@ public static class RdfUris 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 CoscineMetadataExtractionVersion = new("https://purl.org/coscine/terms/metatadataextraction#version"); + public static readonly Uri CoscineProjectsEntity = new("https://purl.org/coscine/projects"); 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 CoscineResourcesEntity = new("https://purl.org/coscine/resources"); 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/");