Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #105: "multiple conformsTo values" #111

Merged
merged 24 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ffaacf4
refactor(reader): reduce complexity
Pfeil May 5, 2023
8cf1d62
fix: avoid memory duplication
Pfeil May 8, 2023
b43c233
docs: move inline comment info to method docs
Pfeil May 8, 2023
8c4ce84
feat: run reader in parallel for large metadata
Pfeil May 8, 2023
7dbbde7
fix(docs): remove docs from old return value
Pfeil May 8, 2023
15878ab
test: read crate with multiple conformsTo values
Pfeil May 8, 2023
bfbdb13
refactor: move generic method into helper class
Pfeil May 23, 2023
8f70ab7
build: add build info to gradle output
Pfeil Jun 20, 2023
a4bd843
Merge remote-tracking branch 'origin/main' into 105-multiple-conforms…
Pfeil Jun 20, 2023
9377c7c
fix: possible exceptions when resolving fake IDs on certain runtime e…
Pfeil Jun 20, 2023
caee947
sonarlint: compare strings using equals
Pfeil Jun 20, 2023
82322f4
feat: enable reading of crates with multiple conformsTo values
Pfeil Jun 20, 2023
e81174b
test: export does not mess up v1.2-DRAFT descriptors
Pfeil Jun 20, 2023
c07f25b
feat: create crates according v1.2-DRAFT, including the multiple conf…
Pfeil Jun 21, 2023
c925b40
cleanup: imports, small todo
Pfeil Jun 21, 2023
21a43ae
fix: javadoc recognized comparison operators as html
Pfeil Jun 21, 2023
1f039dc
docs: add parameter description
Pfeil Jun 21, 2023
295a46a
feat: support draft features on existing crates
Pfeil Jun 23, 2023
6741fc7
fix(docs): inheritDoc in constructors does not work
Pfeil Jun 23, 2023
0d4fd82
refactor: simplify crate builders
Pfeil Jun 23, 2023
123f789
fix(docs): Add missing summary
Pfeil Jun 23, 2023
f6c7602
docs: state compatibility in README.md
Pfeil Jun 23, 2023
ad1e914
feat: convenience methods for comparing versions
Pfeil Jun 23, 2023
025c19e
fix: misleading variable names and useless checks
Pfeil Jun 23, 2023
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
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@
[![Publish to Maven Central / OSSRH](https://github.com/kit-data-manager/ro-crate-java/actions/workflows/publishRelease.yml/badge.svg)](https://github.com/kit-data-manager/ro-crate-java/actions/workflows/publishRelease.yml)

A Java library to create and modify RO-Crates.
Read [Quickstart](#quickstart) for a short overview of the API
The aim of this implementation is to **not** require too deep knowledge of the specification,
and avoiding crates which do not fully comply to the specification, at the same time.
Read [Quick-start](#quick-start) for a short overview of the API
or take a look at [how to adapt the examples from the official specification](#adapting-the-specification-examples).

Build and run tests: `./gradlew build`
Build documentation: `./gradlew javadoc`

On Windows, replace `./gradlew` with `gradlew.bat`.

## RO-Crate Specification Compatibility

- ✅ Version 1.1
- 🛠️ Version 1.2-DRAFT
- ✅ Reading and writing crates with additional profiles or specifications ([examples for reading](src/test/java/edu/kit/datamanager/ro_crate/reader/RoCrateReaderSpec12Test.java), [examples for writing](src/test/java/edu/kit/datamanager/ro_crate/writer/RoCrateWriterSpec12Test.java))
- ✅ Adding profiles or other specifications to a crate ([examples](src/test/java/edu/kit/datamanager/ro_crate/crate/BuilderSpec12Test.java))

## Quick-start
### Example for a basic crate from [RO-Crate website](https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#ro-crate-metadata-file-descriptor)
```java
Expand Down
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ plugins {
group 'edu.kit.datamanager'
description = "A library for easy creation and modification of valid RO-Crates."

println "Running gradle version: $gradle.gradleVersion"
println "Building ${name} version: ${version}"
println "JDK version: ${JavaVersion.current()}"

repositories {
mavenCentral()
}
Expand Down
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
127 changes: 108 additions & 19 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 @@ -8,33 +9,41 @@
import edu.kit.datamanager.ro_crate.context.RoCrateMetadataContext;
import edu.kit.datamanager.ro_crate.entities.AbstractEntity;
import edu.kit.datamanager.ro_crate.entities.contextual.ContextualEntity;
import edu.kit.datamanager.ro_crate.entities.contextual.JsonDescriptor;
import edu.kit.datamanager.ro_crate.entities.data.DataEntity;
import edu.kit.datamanager.ro_crate.entities.data.RootDataEntity;
import edu.kit.datamanager.ro_crate.externalproviders.dataentities.ImportFromDataCite;
import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper;
import edu.kit.datamanager.ro_crate.payload.CratePayload;
import edu.kit.datamanager.ro_crate.payload.RoCratePayload;
import edu.kit.datamanager.ro_crate.preview.CratePreview;
import edu.kit.datamanager.ro_crate.special.CrateVersion;
import edu.kit.datamanager.ro_crate.special.JsonUtilFunctions;
import edu.kit.datamanager.ro_crate.validation.JsonSchemaValidation;
import edu.kit.datamanager.ro_crate.validation.Validator;

import java.io.File;
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.
*
* To build or modify it, use a instance of {@link RoCrateBuilder}. In the case
* features of RO-Crate DRAFT specifications are needed, refer to
* {@link BuilderWithDraftFeatures} and its documentation.
*
* @author Nikola Tzotchev on 6.2.2022 г.
* @version 1
*/
public class RoCrate implements Crate {

private static final String ID = "ro-crate-metadata.json";
private static final String RO_SPEC = "https://w3id.org/ro/crate/1.1";

private final CratePayload roCratePayload;
private CrateMetadataContext metadataContext;
private CratePreview roCratePreview;
Expand Down Expand Up @@ -81,7 +90,7 @@ public RoCrate() {
this.metadataContext = new RoCrateMetadataContext();
rootDataEntity = new RootDataEntity.RootDataEntityBuilder()
.build();
jsonDescriptor = createDefaultJsonDescriptor();
jsonDescriptor = new JsonDescriptor();
}

/**
Expand All @@ -95,12 +104,44 @@ public RoCrate(RoCrateBuilder roCrateBuilder) {
this.metadataContext = roCrateBuilder.metadataContext;
this.roCratePreview = roCrateBuilder.preview;
this.rootDataEntity = roCrateBuilder.rootDataEntity;
this.jsonDescriptor = roCrateBuilder.jsonDescriptor;
this.jsonDescriptor = roCrateBuilder.descriptorBuilder.build();
this.untrackedFiles = roCrateBuilder.untrackedFiles;
Validator defaultValidation = new Validator(new JsonSchemaValidation());
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 @@ -201,28 +242,20 @@ public List<File> getUntrackedFiles() {
return this.untrackedFiles;
}

protected static ContextualEntity createDefaultJsonDescriptor() {
return new ContextualEntity.ContextualEntityBuilder()
.setId(ID)
.addType("CreativeWork")
.addIdProperty("about", "./")
.addIdProperty("conformsTo", RoCrate.RO_SPEC)
.build();
}

/**
* The inner class builder for the easier creation of a ROCrate.
*/
public static final class RoCrateBuilder {
public static class RoCrateBuilder {

CratePayload payload;
CratePreview preview;
CrateMetadataContext metadataContext;
ContextualEntity license;
RootDataEntity rootDataEntity;
ContextualEntity jsonDescriptor;
List<File> untrackedFiles;

JsonDescriptor.Builder descriptorBuilder = new JsonDescriptor.Builder();

/**
* The default constructor of a builder.
*
Expand All @@ -237,7 +270,6 @@ public RoCrateBuilder(String name, String description) {
.addProperty("name", name)
.addProperty("description", description)
.build();
jsonDescriptor = RoCrate.createDefaultJsonDescriptor();
}

/**
Expand All @@ -250,7 +282,6 @@ public RoCrateBuilder() {
this.metadataContext = new RoCrateMetadataContext();
rootDataEntity = new RootDataEntity.RootDataEntityBuilder()
.build();
jsonDescriptor = RoCrate.createDefaultJsonDescriptor();
}

/**
Expand All @@ -263,8 +294,8 @@ public RoCrateBuilder(RoCrate crate) {
this.preview = crate.roCratePreview;
this.metadataContext = crate.metadataContext;
this.rootDataEntity = crate.rootDataEntity;
this.jsonDescriptor = crate.jsonDescriptor;
this.untrackedFiles = crate.untrackedFiles;
this.descriptorBuilder = new JsonDescriptor.Builder(crate);
}

/**
Expand Down Expand Up @@ -322,9 +353,67 @@ public RoCrateBuilder addUntrackedFile(File file) {
return this;
}

/**
* Returns a crate with the information from this builder.
*/
public RoCrate build() {
return new RoCrate(this);
}
}

/**
* Builder for Crates, supporting features which are not in a final
* specification yet.
*
* NOTE: This will change the specification version of your crate.
*
* 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 BuilderWithDraftFeatures extends RoCrateBuilder {

/**
* @see RoCrateBuilder#RoCrateBuilder()
*/
public BuilderWithDraftFeatures() {
super();
}

/**
* @see RoCrateBuilder#RoCrateBuilder(String, String)
*/
public BuilderWithDraftFeatures(String name, String description) {
super();
}

/**
* @see RoCrateBuilder#RoCrateBuilder(RoCrate)
*/
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 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 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;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -306,13 +306,12 @@ protected String getId() {
}

/**
* Setting the id property of the entity.
* Setting the id property of the entity, if the given value is not null.
*
* @param id the String representing the id.
* @return the generic builder.
*/
public T setId(String id) {
// TODO document why this has been implemented this way.
if (id != null) {
this.id = id;
}
Expand Down
Loading