Skip to content

Commit

Permalink
feat: support draft features on existing crates
Browse files Browse the repository at this point in the history
  • Loading branch information
Pfeil committed Jun 23, 2023
1 parent 1f039dc commit 295a46a
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 31 deletions.
29 changes: 29 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 @@ -3,13 +3,15 @@
import java.io.File;
import java.util.Collection;
import java.util.List;
import java.util.Optional;

import edu.kit.datamanager.ro_crate.context.CrateMetadataContext;
import edu.kit.datamanager.ro_crate.entities.AbstractEntity;
import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity;
import edu.kit.datamanager.ro_crate.entities.data.DataEntity;
import edu.kit.datamanager.ro_crate.entities.data.RootDataEntity;
import edu.kit.datamanager.ro_crate.preview.CratePreview;
import edu.kit.datamanager.ro_crate.special.CrateVersion;

/**
* An interface describing an ROCrate.
Expand All @@ -18,6 +20,33 @@
* @version 1
*/
public interface Crate {

/**
* Read version from the crate descriptor and return it as a class
* representation.
*
* NOTE: If there is not version in the crate, it does not comply with the
* specification.
*
* @return the class representation indication the version of this crate, if
* available.
*/
public Optional<CrateVersion> getVersion();

/**
* Returns strings indicating the conformance of a crate with other
* specifications than the RO-Crate version.
*
* If you need the crate version too, refer to {@link #getVersion()}.
*
* This corresponds technically to all conformsTo values, excluding the RO crate
* version / specification.
*
* @return a collection of the profiles or specifications this crate conforms
* to.
*/
public Collection<String> getProfiles();

CratePreview getPreview();

void setMetadataContext(CrateMetadataContext metadataContext);
Expand Down
104 changes: 78 additions & 26 deletions src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package edu.kit.datamanager.ro_crate;

import com.fasterxml.jackson.core.TreeNode;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
Expand All @@ -25,7 +26,12 @@
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;


/**
* The class that represents a single ROCrate.
Expand Down Expand Up @@ -101,6 +107,38 @@ public RoCrate(RoCrateBuilder roCrateBuilder) {
defaultValidation.validate(this);
}

@Override
public Optional<CrateVersion> getVersion() {
JsonNode conformsTo = this.jsonDescriptor.getProperty("conformsTo");
if (conformsTo.isArray()) {
return StreamSupport.stream(conformsTo.spliterator(), false)
.filter(TreeNode::isObject)
.map(obj -> obj.path("@id").asText())
.map(CrateVersion::fromSpecUri)
.filter(Optional::isPresent)
.map(Optional::get)
.findFirst();
} else if (conformsTo.isObject()) {
return CrateVersion.fromSpecUri(conformsTo.get("@id").asText());
} else {
return Optional.empty();
}
}

@Override
public Collection<String> getProfiles() {
JsonNode conformsTo = this.jsonDescriptor.getProperty("conformsTo");
if (conformsTo.isArray()) {
return StreamSupport.stream(conformsTo.spliterator(), false)
.filter(TreeNode::isObject)
.map(obj -> obj.path("@id").asText())
.filter(txt -> !CrateVersion.fromSpecUri(txt).isPresent())
.collect(Collectors.toSet());
} else {
return Collections.emptySet();
}
}

@Override
public String getJsonMetadata() {
ObjectMapper objectMapper = MyObjectMapper.getMapper();
Expand Down Expand Up @@ -319,49 +357,63 @@ public RoCrate build() {
}

/**
* Builder for Crates, supporting all Features from v1.1 on.
*/
public static class BuilderV1p1 extends RoCrateBuilder {
// for consistency
}

/**
* Builder for Crates, supporting all Features from v1.2 on.
* Builder for Crates, supporting features which are not in a final
* specification yet.
*
* NOTE: This will change the specification version of your crate.
*
* NOTE: Changes may happen as this is a draft!
* We only add features we expect to be in the new specification in the
* end.
* In case a feature will not make it into the specification, we will mark it as
* deprecated and remove it in new major versions.
* If a feature is finalized, it will be added to the stable
* {@link RoCrateBuilder} and marked as deprecated in this class.
*/
public static class BuilderV1p2Draft extends RoCrateBuilder {
public static class BuilderWithDraftFeatures extends RoCrateBuilder {

JsonDescriptor.Builder descriptorBuilder = new JsonDescriptor.Builder()
.setVersion(CrateVersion.V1P2_DRAFT);
JsonDescriptor.Builder descriptorBuilder = new JsonDescriptor.Builder();

/**
* A default constructor without any params where the root data entity will be
* plain.
* {@inheritDoc}
* @see RoCrateBuilder#RoCrateBuilder()
*/
public BuilderV1p2Draft() {
this.payload = new RoCratePayload();
this.untrackedFiles = new ArrayList<>();
this.metadataContext = new RoCrateMetadataContext();
rootDataEntity = new RootDataEntity.RootDataEntityBuilder()
.build();
jsonDescriptor = new JsonDescriptor.Builder()
.setVersion(CrateVersion.V1P2_DRAFT)
.build();
public BuilderWithDraftFeatures() {
super();
}

/**
* {@inheritDoc}
* @param name {@inheritDoc}
* @param description {@inheritDoc}
*/
public BuilderWithDraftFeatures(String name, String description) {
super();
}

/**
* {@inheritDoc}
* @param crate {@inheritDoc}
*/
public BuilderWithDraftFeatures(RoCrate crate) {
super(crate);
this.descriptorBuilder = new JsonDescriptor.Builder(crate);
}

/**
* Indicate this crate also conforms to the given specification, in addition to
* the version this builder creates.
* the version this builder adds.
*
* This is helpful for profiles or other specifications the crate conforms to.
* Can be called multiple times to add more specifications.
*
* @param specification a specification or profile this crate conforms to.
* @return the builder
*/
public BuilderV1p2Draft alsoConformsTo(URI specification) {
descriptorBuilder.addConformsTo(specification);
public BuilderWithDraftFeatures alsoConformsTo(URI specification) {
descriptorBuilder
.addConformsTo(specification)
// usage of a draft feature results in draft version numbers of the crate
.setVersion(CrateVersion.LATEST_UNSTABLE);
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;

import edu.kit.datamanager.ro_crate.Crate;
import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper;
import edu.kit.datamanager.ro_crate.special.CrateVersion;

Expand Down Expand Up @@ -45,6 +46,15 @@ public static final class Builder {
CrateVersion version = CrateVersion.LATEST_STABLE;
Set<String> otherConformsToValues = new HashSet<>();

public Builder() {
// default
}

public Builder(Crate crate) {
crate.getVersion().ifPresent(v -> this.version = v);
this.otherConformsToValues.addAll(crate.getProfiles());
}

public Builder setVersion(CrateVersion version) {
this.version = version;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public enum CrateVersion {
* https://w3id.org/ro/crate/1.1
* @return the matching CrateVersion enum, if the URI matches any. Empty if not.
*/
public Optional<CrateVersion> fromSpecUri(String conformsTo) {
public static Optional<CrateVersion> fromSpecUri(String conformsTo) {
return Optional.ofNullable(crateVersionOfConformsTo(conformsTo));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,82 @@
package edu.kit.datamanager.ro_crate.crate;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;

import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.databind.JsonNode;

import edu.kit.datamanager.ro_crate.Crate;
import edu.kit.datamanager.ro_crate.RoCrate;
import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity;
import edu.kit.datamanager.ro_crate.reader.FolderReader;
import edu.kit.datamanager.ro_crate.reader.RoCrateReader;
import edu.kit.datamanager.ro_crate.special.CrateVersion;
import edu.kit.datamanager.ro_crate.validation.JsonSchemaValidation;
import edu.kit.datamanager.ro_crate.validation.Validator;

class BuilderSpec12Test {
private URI profile1;
private URI profile2;

@Test
void testAppendConformsTo() throws URISyntaxException {
Crate crate = new RoCrate.BuilderV1p2Draft()
.alsoConformsTo(new URI("https://w3id.org/ro/wfrun/process/0.1"))
.alsoConformsTo(new URI("https://example.com/myprofile/1.0"))
.build();
Crate crate = new RoCrate.BuilderWithDraftFeatures()
.alsoConformsTo(new URI("https://w3id.org/ro/wfrun/process/0.1"))
.alsoConformsTo(new URI("https://example.com/myprofile/1.0"))
.build();
JsonNode conformsTo = crate.getJsonDescriptor().getProperty("conformsTo");
assertTrue(conformsTo.isArray());
// one version and two profiles
assertEquals(1 + 2, conformsTo.size());
}

@Test
void testModificationOfDraftCrate() throws URISyntaxException {
String path = this.getClass().getResource("/crates/spec-1.2-DRAFT/minimal-with-conformsTo-Array").getPath();
RoCrate crate = new RoCrateReader(new FolderReader()).readCrate(path);
Collection<String> existingProfiles = crate.getProfiles();
profile1 = new URI("https://example.com/myprofile/1.0");
profile2 = new URI("https://example.com/myprofile/2.0");
// the loaded crate has at least one profile
assertFalse(existingProfiles.isEmpty());
// and the ones we will add later are not part of it
assertFalse(existingProfiles.contains(profile1.toString()));
assertFalse(existingProfiles.contains(profile2.toString()));

// add profiles
RoCrate modifiedCrate = new RoCrate.BuilderWithDraftFeatures(crate)
.alsoConformsTo(profile1)
.alsoConformsTo(profile2)
.addContextualEntity(new ContextualEntity.ContextualEntityBuilder()
.addType("CreativeWork")
.build())
.build();

// sanity checks
Validator defaultValidation = new Validator(new JsonSchemaValidation());
assertTrue(defaultValidation.validate(modifiedCrate));
assertEquals(CrateVersion.LATEST_UNSTABLE, crate.getVersion().get());
assertEquals(CrateVersion.LATEST_UNSTABLE, modifiedCrate.getVersion().get());

// number of profiles increased by 2
Collection<String> newProfileState = modifiedCrate.getProfiles();
assertEquals(existingProfiles.size() + 2, newProfileState.size());
// new profiles are present
newProfileState.contains(profile1.toString());
newProfileState.contains(profile2.toString());
// old profiles are present
assertEquals(
0,
existingProfiles.stream()
.filter(txt -> !newProfileState.contains(txt))
.count()
);
}
}

0 comments on commit 295a46a

Please sign in to comment.