Skip to content

Commit b90d3c8

Browse files
committed
feat: add support for automatic provenance management in CrateWriter
1 parent b855b80 commit b90d3c8

2 files changed

Lines changed: 107 additions & 0 deletions

File tree

src/main/java/edu/kit/datamanager/ro_crate/writer/CrateWriter.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,17 @@
1515
public class CrateWriter<DESTINATION_TYPE> {
1616

1717
private final GenericWriterStrategy<DESTINATION_TYPE> strategy;
18+
protected boolean automaticProvenance = true;
1819

1920
public CrateWriter(GenericWriterStrategy<DESTINATION_TYPE> strategy) {
2021
this.strategy = strategy;
2122
}
2223

24+
public CrateWriter<DESTINATION_TYPE> withAutomaticProvenance(boolean automaticProvenance) {
25+
this.automaticProvenance = automaticProvenance;
26+
return this;
27+
}
28+
2329
/**
2430
* This method saves the crate to a destination provided.
2531
*
@@ -29,6 +35,9 @@ public CrateWriter(GenericWriterStrategy<DESTINATION_TYPE> strategy) {
2935
public void save(Crate crate, DESTINATION_TYPE destination) throws IOException {
3036
Validator defaultValidation = new Validator(new JsonSchemaValidation());
3137
defaultValidation.validate(crate);
38+
if (automaticProvenance) {
39+
new ProvenanceManager().addProvenanceInformation(crate);
40+
}
3241
this.strategy.save(crate, destination);
3342
}
3443
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package edu.kit.datamanager.ro_crate.writer;
2+
3+
import edu.kit.datamanager.ro_crate.Crate;
4+
import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity;
5+
import java.time.Instant;
6+
import java.util.Collection;
7+
import java.util.Map;
8+
import java.util.UUID;
9+
10+
/**
11+
* Manages provenance information for RO-Crates.
12+
* Handles the creation and updating of ro-crate-java entity and its actions.
13+
*/
14+
class ProvenanceManager {
15+
private static final String RO_CRATE_JAVA_ID = "#ro-crate-java";
16+
17+
void addProvenanceInformation(Crate crate) {
18+
// Determine if this is the first write
19+
boolean isFirstWrite = !crate.getJsonMetadata().contains(RO_CRATE_JAVA_ID);
20+
21+
// Create action entity first
22+
String actionId = "#" + UUID.randomUUID();
23+
ContextualEntity actionEntity = createActionEntity(actionId, isFirstWrite);
24+
25+
// Create or update ro-crate-java entity
26+
ContextualEntity roCrateJavaEntity = buildRoCrateJavaEntity(crate, actionId, isFirstWrite);
27+
28+
// Add entities to crate in correct order (referenced entity first)
29+
crate.addContextualEntity(roCrateJavaEntity);
30+
crate.addContextualEntity(actionEntity);
31+
}
32+
33+
private ContextualEntity createActionEntity(String actionId, boolean isFirstWrite) {
34+
return new ContextualEntity.ContextualEntityBuilder()
35+
.setId(actionId)
36+
.addType(isFirstWrite ? "CreateAction" : "UpdateAction")
37+
.addProperty("startTime", Instant.now().toString())
38+
.addIdProperty("agent", RO_CRATE_JAVA_ID)
39+
.build();
40+
}
41+
42+
private ContextualEntity buildRoCrateJavaEntity(Crate crate, String newActionId, boolean isFirstWrite) {
43+
ContextualEntity.ContextualEntityBuilder builder = new ContextualEntity.ContextualEntityBuilder()
44+
.setId(RO_CRATE_JAVA_ID)
45+
.addType("SoftwareApplication")
46+
.addProperty("name", "ro-crate-java")
47+
.addProperty("url", "https://github.com/kit-data-manager/ro-crate-java")
48+
.addProperty("version", "1.0.0")
49+
.addProperty("softwareVersion", "1.0.0")
50+
.addProperty("license", "Apache-2.0")
51+
.addProperty("description", "A Java library for creating and manipulating RO-Crates");
52+
53+
if (isFirstWrite) {
54+
builder.addIdProperty("action", newActionId);
55+
} else {
56+
Collection<ContextualEntity> entities = crate.getAllContextualEntities();
57+
for (ContextualEntity entity : entities) {
58+
if (RO_CRATE_JAVA_ID.equals(entity.getId())) {
59+
addActionToBuilder(builder, entity, newActionId);
60+
break;
61+
}
62+
}
63+
}
64+
65+
return builder.build();
66+
}
67+
68+
private void addActionToBuilder(
69+
ContextualEntity.ContextualEntityBuilder builder,
70+
ContextualEntity existingEntity,
71+
String newActionId
72+
) {
73+
Object existingAction = existingEntity.getProperty("action");
74+
if (existingAction == null) {
75+
builder.addIdProperty("action", newActionId);
76+
return;
77+
}
78+
79+
// When there are existing actions, we need to preserve them
80+
if (existingAction instanceof Map) {
81+
// Single previous action (as a Map containing @id)
82+
String existingActionId = ((Map<?, ?>) existingAction).get("@id").toString();
83+
builder.addIdProperty("action", "#" + existingActionId);
84+
builder.addIdProperty("action", newActionId);
85+
} else if (existingAction instanceof Collection<?> oldActions) {
86+
// Multiple previous actions -> Add all existing actions
87+
oldActions.stream()
88+
.map(action -> ((Map<?, ?>) action).get("@id").toString())
89+
.map(id -> !id.startsWith("#") ? "#" + id : id)
90+
.forEach(id -> builder.addIdProperty("action", id));
91+
// Add the new action
92+
builder.addIdProperty("action", newActionId);
93+
} else {
94+
// Unexpected format, just add the new action
95+
builder.addIdProperty("action", newActionId);
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)