Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
11fc59c
test: add rough tests for future RO-Crate metadata generation
Pfeil Jun 3, 2025
b594997
test: refine first tests for future RO-Crate metadata generation
Pfeil Jun 3, 2025
5e5a37c
test: add bidirectional relation test for implicit crate metadata
Pfeil Jun 3, 2025
bb170bb
test: add test for accumulating actions during multiple writes to RO-…
Pfeil Jun 3, 2025
16002a6
test: add debugging output for metadata in RO-Crate tests
Pfeil Jun 3, 2025
a08c314
test: add validation tests for RO-Crate version format and completeness
Pfeil Jun 3, 2025
4e817e9
test: add provenance tests for modifying RO-Crate metadata
Pfeil Jun 3, 2025
b855b80
test: add validation for RO-Crate metadata after writing and modifyin…
Pfeil Jun 3, 2025
b90d3c8
feat: add support for automatic provenance management in CrateWriter
Pfeil Jun 3, 2025
cc97444
chore: add TODO for reading software version from Gradle in Provenanc…
Pfeil Jun 3, 2025
2221627
fix: standardize property name to 'Action' in ProvenanceManager and r…
Pfeil Jun 4, 2025
b36e64b
feat: add imported state management to Crate interface and implementa…
Pfeil Jun 4, 2025
fe63d12
feat: refactor buildRoCrateJavaEntity to streamline entity creation a…
Pfeil Jun 4, 2025
f2086f3
fix: wrong assumptions / typos in RoCrateMetadataGenerationTest
Pfeil Jun 4, 2025
7d3020c
fix(test): disable automatic provenance in tests unrelated to metadat…
Pfeil Jun 4, 2025
7257354
feat: add version properties generation and load version from propert…
Pfeil Jun 4, 2025
e60ae89
chore: update version to 2.1.0-rc3 in CITATION.cff
Pfeil Jun 4, 2025
db56685
fix: improve error handling in loadVersionFromProperties method
Pfeil Jun 5, 2025
501060d
chore: implement ClasspathPropertiesVersionProvider for version retri…
Pfeil Jun 5, 2025
55648fd
feat: create provenance entity per library version and keep old ones
Pfeil Jun 5, 2025
0fba28d
feat: action entities in provenance now actually call the crate their…
Pfeil Jun 5, 2025
fe179bf
feat: add utility class for handling RO-Crate graph operations
Pfeil Jun 5, 2025
f4046ea
test: add unit tests for ProvenanceManager library ID retrieval
Pfeil Jun 5, 2025
9656ea7
feat: add getIdProperty method to retrieve a nested id property value…
Pfeil Jun 5, 2025
6cbbbc8
test: add unit tests for ProvenanceManager
Pfeil Jun 5, 2025
77c4279
refactor: update withAutomaticProvenance method to accept ProvenanceM…
Pfeil Jun 5, 2025
3b47ecc
doc: complete javadocs for CrateWriter
Pfeil Jun 5, 2025
c42b185
test: add delay in RoCrateMetadataGenerationTest to ensure crate stab…
Pfeil Jun 5, 2025
210d6ac
refactor: reduce possibly missing code coverage
Pfeil Jun 5, 2025
eb89c94
chore: fix small linter complaints in AbstractEntity and ClasspathPro…
Pfeil Jun 5, 2025
9cbe059
test: add sleep delays in RoCrateMetadataGenerationTest to ensure sta…
Pfeil Jun 5, 2025
ec9e9da
doc: enhance documentation in ProvenanceManager for clarity and consi…
Pfeil Jun 5, 2025
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
2 changes: 1 addition & 1 deletion CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ authors:
given-names: "Sabrine"
orcid: "https://orcid.org/0000-0002-4480-6116"
title: "ro-crate-java"
version: 1.0.3
version: 2.1.0-rc3
date-released: 2022-07-19
url: "https://github.com/kit-data-manager/ro-crate-java"
21 changes: 21 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,22 @@ configurations {
performanceTestImplementation.extendsFrom implementation
}

// Task for creating a resource file with the version info
tasks.register("generateVersionProps", WriteProperties) { t ->
def generatedResourcesDir = project.layout.buildDirectory.dir(["resources", "main"].join(File.separator))
def outputFile = generatedResourcesDir.map { it.file("version.properties") }

t.destinationFile = outputFile.get().asFile
t.property("version", version)
}

tasks.register("generateVersionPropsTest", WriteProperties) { t ->
def generatedResourcesDir = project.layout.buildDirectory.dir(["resources", "test"].join(File.separator))
def outputFile = generatedResourcesDir.map { it.file("version.properties") }

t.destinationFile = outputFile.get().asFile
t.property("version", version)
}

tasks.register('performanceContextEntitiesBenchmark', JavaExec) {
description = "Run the context entities benchmarks."
Expand Down Expand Up @@ -154,8 +170,13 @@ tasks.register('performanceReadWriteMultipleCratesBenchmark', JavaExec) {
mainClass = 'edu.kit.datamanager.ro_crate.multiplecrates.MultipleCratesWriteAndRead'
}

compileJava {
dependsOn generateVersionProps
}

test {
useJUnitPlatform()
dependsOn generateVersionPropsTest
finalizedBy jacocoTestReport
}

Expand Down
22 changes: 22 additions & 0 deletions src/main/java/edu/kit/datamanager/ro_crate/Crate.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,28 @@
*/
public interface Crate {

/**
* Mark the crate as imported, i.e. it has been read from a file
* or is for other reasons not considered a new crate.
* <p>
* This is useful mostly for readers to indicate this in case
* the crate may not have any provenance information yet and
* should still be recognized as an imported crate.
*
* @return this crate, for convenience.
*/
Crate markAsImported();

/**
* Check if the crate is marked as imported.
* <p>
* If true, it indicates that the crate has been read from a file
* or is for other reasons not considered a new crate.
*
* @return true if the crate is marked as imported, false otherwise.
*/
boolean isImported();

/**
* Read version from the crate descriptor and return it as a class
* representation.
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ public class RoCrate implements Crate {

protected Collection<File> untrackedFiles;

/**
* Indicates whether this crate has been imported from an external source.
* This is used to determine if ro-crate-java should add a CreateAction
* or an UpdateAction in the provenance on export.
*/
protected boolean isImported = false;

@Override
public RoCrate markAsImported() {
this.isImported = true;
return this;
}

@Override
public boolean isImported() {
return this.isImported;
}

@Override
public CratePreview getPreview() {
return this.roCratePreview;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,19 @@ public JsonNode getProperty(String propertyKey) {
return this.properties.get(propertyKey);
}

/**
* Returns the value of the property with the given key as a String.
* If the property is not found, it returns null.
*
* @param propertyKey the key of the property.
* @return the value of the property as a String or null if not found.
*/
public String getIdProperty(String propertyKey) {
return Optional.ofNullable(this.properties.get(propertyKey))
.map(jsonNode -> jsonNode.path("@id").asText(null))
.orElse(null);
}

@JsonIgnore
public String getId() {
JsonNode id = this.properties.get("@id");
Expand Down Expand Up @@ -241,9 +254,7 @@ private static boolean addProperty(ObjectNode whereToAdd, String key, JsonNode v
public void addIdProperty(String name, String id) {
if (id == null || id.isBlank()) { return; }
mergeIdIntoValue(id, this.properties.get(name))
.ifPresent(newValue -> {
this.properties.set(name, newValue);
});
.ifPresent(newValue -> this.properties.set(name, newValue));
this.linkedTo.add(id);
this.notifyObservers();
}
Expand Down Expand Up @@ -356,7 +367,7 @@ private static void checkFormatISO8601(String date) throws IllegalArgumentExcept
/**
* Adds a property with date time format. The property should match the ISO 8601
* date format.
*
* <p>
* Same as {@link #addProperty(String, String)} but with internal check.
*
* @param key key of the property (e.g. datePublished)
Expand Down Expand Up @@ -411,7 +422,7 @@ public T setId(String id) {
if (IdentifierUtils.isValidUri(id)) {
this.id = id;
} else {
this.id = IdentifierUtils.encode(id).get();
this.id = IdentifierUtils.encode(id).orElse(this.id);
}
}
return self();
Expand Down Expand Up @@ -448,7 +459,7 @@ public T addTypes(Collection<String> types) {
/**
* Adds a property with date time format. The property should match the ISO 8601
* date format.
*
* <p>
* Same as {@link #addProperty(String, String)} but with internal check.
*
* @param key key of the property (e.g. datePublished)
Expand Down Expand Up @@ -508,7 +519,7 @@ public T addProperty(String key, boolean value) {
/**
* ID properties are often used when referencing other entities within
* the ROCrate. This method adds automatically such one.
*
* <p>
* Instead of {@code "name": "id" }
* this will add {@code "name" : {"@id": "id"} }
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ default void generate(Crate crate, File targetDir) throws IOException {
// as this is usually called in the process of writing a crate
// (including preview)
new CrateWriter<>(new WriteFolderStrategy().disablePreview())
// We assume the caller (e.g. a writer) already stored the provenance.
.withAutomaticProvenance(null)
.save(crate, targetDir.getAbsolutePath());
this.saveAllToFolder(targetDir);
try (var stream = Files.list(targetDir.toPath())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ public RoCrate readCrate(T location) throws IOException {
usedFiles.add(files.toPath().resolve(FILE_METADATA_JSON).toFile().getPath());
usedFiles.add(files.toPath().resolve(FILE_PREVIEW_HTML).toFile().getPath());
usedFiles.add(files.toPath().resolve(FILE_PREVIEW_FILES).toFile().getPath());
return rebuildCrate(metadataJson, files, usedFiles);
return rebuildCrate(metadataJson, files, usedFiles).markAsImported();
}

private RoCrate rebuildCrate(ObjectNode metadataJson, File files, HashSet<String> usedFiles) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package edu.kit.datamanager.ro_crate.util;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Properties;

public class ClasspathPropertiesVersionProvider implements VersionProvider {
public static final String VERSION_PROPERTIES = "version.properties";

/**
* Cached version to avoid repeated file/resource reads.
*/
private String cachedVersion = null;

/**
* Constructs a ClasspathPropertiesVersionProvider that reads the version from a properties file in the classpath.
*/
public ClasspathPropertiesVersionProvider() {
this.cachedVersion = getVersion();
}

@Override
public String getVersion() {
if (cachedVersion != null) {
return cachedVersion;
}

URL resource = this.getClass().getResource("/" + VERSION_PROPERTIES);
assert resource != null : VERSION_PROPERTIES + " not found in classpath";

try (InputStream input = resource.openStream()) {
Properties properties = new Properties();
properties.load(input);
String version = properties.getProperty("version");
assert version != null : "Version property not found in " + VERSION_PROPERTIES;
return version.trim();
} catch (IOException e) {
throw new IllegalStateException("Failed to read version from properties file", e);
}
}
}
61 changes: 61 additions & 0 deletions src/main/java/edu/kit/datamanager/ro_crate/util/Graph.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package edu.kit.datamanager.ro_crate.util;

import com.fasterxml.jackson.databind.JsonNode;

import java.util.stream.StreamSupport;

/**
* Utility class for handling operations on RO-Crate graphs.
* Provides methods to find entities by ID or type within a graph.
* <p>
* {@see JsonUtilFunctions}.
*/
public class Graph {

private Graph() {
// Private constructor to prevent instantiation
}

/**
* Finds an entity in the graph by its ID.
*
* @param graph The JSON node representing the graph.
* @param id The ID of the entity to find.
* @return The entity as a JsonNode if found, null otherwise.
*/
public static JsonNode findEntityById(JsonNode graph, String id) {
for (JsonNode entity : graph) {
if (entity.has("@id") && entity.get("@id").asText().equals(id)) {
return entity;
}
}
return null;
}

/**
* Finds an entity in the graph by its type.
*
* @param graph The JSON node representing the graph.
* @param type The type of the entity to find.
* @return The entity as a JsonNode if found, null otherwise.
*/
public static JsonNode findEntityByType(JsonNode graph, String type) {
return StreamSupport.stream(graph.spliterator(), false)
.filter(entity -> entity.path("@type").asText().equals(type))
.findFirst()
.orElse(null);
}

/**
* Finds all entities in the graph by their type.
*
* @param graph The JSON node representing the graph.
* @param type The type of the entities to find.
* @return An array of JsonNode containing all entities of the specified type.
*/
public static JsonNode[] findEntitiesByType(JsonNode graph, String type) {
return StreamSupport.stream(graph.spliterator(), false)
.filter(entity -> entity.path("@type").asText().equals(type))
.toArray(JsonNode[]::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package edu.kit.datamanager.ro_crate.util;

public interface VersionProvider {
/**
* Returns the version of the ro-crate-java library.
*
* @return The version string.
*/
String getVersion();
}
21 changes: 21 additions & 0 deletions src/main/java/edu/kit/datamanager/ro_crate/writer/CrateWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,29 @@
public class CrateWriter<DESTINATION_TYPE> {

private final GenericWriterStrategy<DESTINATION_TYPE> strategy;
protected ProvenanceManager provenanceManager = new ProvenanceManager();

/**
* Constructs a CrateWriter with a specified strategy for writing crates.
*
* @param strategy the strategy to use for writing crates.
*/
public CrateWriter(GenericWriterStrategy<DESTINATION_TYPE> strategy) {
this.strategy = strategy;
}

/**
* Sets the ProvenanceManager to be used for automatic provenance information
* generation when saving the crate.
*
* @param provenanceManager the ProvenanceManager to use. Using null will disable provenance management.
* @return this CrateWriter instance for method chaining.
*/
public CrateWriter<DESTINATION_TYPE> withAutomaticProvenance(ProvenanceManager provenanceManager) {
this.provenanceManager = provenanceManager;
return this;
}

/**
* This method saves the crate to a destination provided.
*
Expand All @@ -29,6 +47,9 @@ public CrateWriter(GenericWriterStrategy<DESTINATION_TYPE> strategy) {
public void save(Crate crate, DESTINATION_TYPE destination) throws IOException {
Validator defaultValidation = new Validator(new JsonSchemaValidation());
defaultValidation.validate(crate);
if (this.provenanceManager != null) {
new ProvenanceManager().addProvenanceInformation(crate);
}
this.strategy.save(crate, destination);
}
}
Loading
Loading