Select Git revision
evaluate.py
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
StructuralData.cs 8.66 KiB
using Coscine.ApiClient;
using Coscine.ApiClient.Core.Api;
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;
/// <summary>
/// Provides an abstract base class for handling structural data transformations and migrations.
/// </summary>
/// <typeparam name="S">The type of data to be transformed or migrated.</typeparam>
/// <remarks>
/// This class is partially shared between this script and the Trellis Migrator script.
/// </remarks>
public abstract class StructuralData<S>
{
private readonly string _adminToken;
protected readonly Configuration _apiConfiguration;
protected readonly AdminApi _adminApi;
protected readonly PidConfiguration _pidConfiguration; // Comes from the API Client, not from the application's own configuration
/// <summary>
/// Initializes a new instance of the <see cref="StructuralData{S}"/> class.
/// </summary>
public StructuralData()
{
// Retrieve the configuration settings for the PID ePIC API from the API Client
var apiConfiguration = ApiConfigurationUtil.RetrieveApiConfiguration();
_pidConfiguration = apiConfiguration.GetSection(PidConfiguration.Section).Get<PidConfiguration>() ?? new();
// Ensiure that the prefix is not null or empty.
ArgumentException.ThrowIfNullOrWhiteSpace(_pidConfiguration.Prefix, nameof(_pidConfiguration.Prefix));
// Generate an admin token for the API Client
var jwtConfiguration = ApiConfigurationUtil.RetrieveJwtConfiguration();
_adminToken = ApiConfigurationUtil.GenerateAdminToken(jwtConfiguration);
_apiConfiguration = new Configuration()
{
BasePath = "http://localhost:7206/coscine",
ApiKeyPrefix = { { "Authorization", "Bearer" } },
ApiKey = { { "Authorization", _adminToken } },
Timeout = 300000, // 5 minutes
};
_adminApi = new AdminApi(_apiConfiguration);
}
/// <summary>
/// Asynchronously converts a collection of entries into linked data graphs.
/// </summary>
/// <param name="entries">A collection of entries of type <typeparamref name="S"/> to convert.</param>
/// <typeparam name="S">The type of the entries to be converted into linked data graphs.</typeparam>
/// <returns>An asynchronous enumerable (<see cref="IAsyncEnumerable{IGraph}"/>) of graphs, where each graph represents the linked data derived from an entry.</returns>
public abstract IAsyncEnumerable<IGraph> ConvertToLinkedDataAsync(IAsyncEnumerable<S> entries);
/// <summary>
/// Asynchronously retrieves all entries of type <typeparamref name="S"/>.
/// </summary>
/// <typeparam name="S">The type of the entries to retrieve.</typeparam>
/// <returns>An <see cref="IAsyncEnumerable{S}"/> representing an asynchronous stream of entries.</returns>
public abstract IAsyncEnumerable<S> GetAll();
/// <summary>
/// Asynchronously migrates data, with an option to simulate the migration process without making any changes (dummy mode).
/// </summary>
/// <param name="dummyMode">If set to <c>true</c>, the method simulates the migration process without executing any changes to the data store.</param>
/// <returns>A task representing the asynchronous migration operation.</returns>
public async Task Migrate(bool dummyMode)
{
var spacer = new string('-', 35);
Console.WriteLine($"\n{spacer}\n{typeof(S).Name}\n{spacer}");
var graphs = ConvertToLinkedDataAsync(GetAll());
if (!dummyMode)
{
await StoreGraphs(graphs);
}
}
/// <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();
}
}
}
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)
)
);
}
}
}