diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index ba8a9308..fe8a5ce3 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -11,7 +11,6 @@ on: push: branches: [ main, development ] pull_request: - branches: [ main, development ] workflow_dispatch: env: diff --git a/src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java b/src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java index 330135b7..e794974c 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/RoCrate.java @@ -10,13 +10,10 @@ 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.contextual.OrganizationEntity; import edu.kit.datamanager.ro_crate.entities.data.DataEntity; -import edu.kit.datamanager.ro_crate.entities.data.DataEntity.DataEntityBuilder; -import edu.kit.datamanager.ro_crate.entities.data.FileEntity; + import edu.kit.datamanager.ro_crate.entities.data.RootDataEntity; import edu.kit.datamanager.ro_crate.externalproviders.dataentities.ImportFromDataCite; -import edu.kit.datamanager.ro_crate.externalproviders.organizationprovider.RorProvider; import edu.kit.datamanager.ro_crate.objectmapper.MyObjectMapper; import edu.kit.datamanager.ro_crate.payload.CratePayload; import edu.kit.datamanager.ro_crate.payload.RoCratePayload; @@ -26,12 +23,9 @@ 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 edu.kit.datamanager.ro_crate.writer.FolderWriter; -import edu.kit.datamanager.ro_crate.writer.RoCrateWriter; import java.io.File; import java.net.URI; -import java.nio.file.Paths; import java.util.*; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -354,6 +348,11 @@ public RoCrateBuilder addName(String name) { return this; } + public RoCrateBuilder addIdentifier(String identifier) { + this.rootDataEntity.addProperty("identifier", identifier.strip()); + return this; + } + public RoCrateBuilder addDescription(String description) { this.rootDataEntity.addProperty(PROPERTY_DESCRIPTION, description); return this; diff --git a/src/main/java/edu/kit/datamanager/ro_crate/entities/AbstractEntity.java b/src/main/java/edu/kit/datamanager/ro_crate/entities/AbstractEntity.java index 580e625b..f963f42a 100644 --- a/src/main/java/edu/kit/datamanager/ro_crate/entities/AbstractEntity.java +++ b/src/main/java/edu/kit/datamanager/ro_crate/entities/AbstractEntity.java @@ -239,33 +239,60 @@ private static boolean addProperty(ObjectNode whereToAdd, String key, JsonNode v * @param id the "id" of the property. */ public void addIdProperty(String name, String id) { - JsonNode jsonNode = addToIdProperty(name, id, this.properties.get(name)); - if (jsonNode != null) { - this.linkedTo.add(id); - this.properties.set(name, jsonNode); - this.notifyObservers(); - } + mergeIdIntoValue(id, this.properties.get(name)) + .ifPresent(newValue -> { + this.linkedTo.add(id); + this.properties.set(name, newValue); + this.notifyObservers(); + }); } - private static JsonNode addToIdProperty(String name, String id, JsonNode property) { - ObjectMapper objectMapper = MyObjectMapper.getMapper(); - if (name != null && id != null) { - if (property == null) { - return objectMapper.createObjectNode().put("@id", id); - } else { - if (property.isArray()) { - ArrayNode ns = (ArrayNode) property; - ns.add(objectMapper.createObjectNode().put("@id", id)); - return ns; - } else { - ArrayNode newNodes = objectMapper.createArrayNode(); - newNodes.add(property); - newNodes.add(objectMapper.createObjectNode().put("@id", id)); - return newNodes; - } - } + /** + * Merges the given id into the current value, + * using this representation: {"@id" : "id"}. + *

+ * The current value can be null without errors. + * Only the id will be considered in this case. + *

+ * If the id is null-ish, it will not be added, similar to a null-ish value. + * If the id is already present, nothing will be done. + * If it is not an array and the id is not present, an array will be applied. + * + * @param id the id to add. + * @param currentValue the current value of the property. + * @return The updated value of the property. + * Empty if value does not change! + */ + private static Optional mergeIdIntoValue(String id, JsonNode currentValue) { + if (id == null || id.isBlank()) { return Optional.empty(); } + + ObjectMapper jsonBuilder = MyObjectMapper.getMapper(); + ObjectNode newIdObject = jsonBuilder.createObjectNode().put("@id", id); + if (currentValue == null || currentValue.isNull() || currentValue.isMissingNode()) { + return Optional.ofNullable(newIdObject); + } + + boolean isIdAlready = currentValue.asText().equals(id); + boolean isIdObjectAlready = currentValue.path("@id").asText().equals(id); + boolean isArrayWithIdPresent = currentValue.valueStream() + .anyMatch(node -> node + .path("@id") + .asText() + .equals(id)); + if (isIdAlready || isIdObjectAlready || isArrayWithIdPresent) { + return Optional.empty(); + } + + if (currentValue.isArray() && currentValue instanceof ArrayNode currentValueAsArray) { + currentValueAsArray.add(newIdObject); + return Optional.of(currentValueAsArray); + } else { + // property is not an array, so we make it an array + ArrayNode newNodes = jsonBuilder.createArrayNode(); + newNodes.add(currentValue); + newNodes.add(newIdObject); + return Optional.of(newNodes); } - return null; } /** @@ -486,11 +513,11 @@ public T addProperty(String key, boolean value) { * @return the generic builder */ public T addIdProperty(String name, String id) { - JsonNode jsonNode = AbstractEntity.addToIdProperty(name, id, this.properties.get(name)); - if (jsonNode != null) { - this.properties.set(name, jsonNode); - this.relatedItems.add(id); - } + AbstractEntity.mergeIdIntoValue(id, this.properties.get(name)) + .ifPresent(newValue -> { + this.properties.set(name, newValue); + this.relatedItems.add(id); + }); return self(); } diff --git a/src/test/java/edu/kit/datamanager/ro_crate/HelpFunctions.java b/src/test/java/edu/kit/datamanager/ro_crate/HelpFunctions.java index 97d29c58..d1d4aab6 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/HelpFunctions.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/HelpFunctions.java @@ -34,8 +34,7 @@ public static void compareEntityWithFile(AbstractEntity entity, String string) t public static void compare(JsonNode node1, JsonNode node2, Boolean equals) { var comparator = new JsonComparator() { - public boolean compareValues(Object expected, Object actual) { - + public boolean compareValues(Object expected, Object actual) { return expected.equals(actual); } @@ -43,6 +42,7 @@ public boolean compareFields(String expected, String actual) { return expected.equals(actual); } }; + if (equals) { JSONCompare.assertMatches(node1, node2, comparator); } else { diff --git a/src/test/java/edu/kit/datamanager/ro_crate/examples/ExamplesOfSpecificationV1p1Test.java b/src/test/java/edu/kit/datamanager/ro_crate/examples/ExamplesOfSpecificationV1p1Test.java index c3997823..8d1525c9 100644 --- a/src/test/java/edu/kit/datamanager/ro_crate/examples/ExamplesOfSpecificationV1p1Test.java +++ b/src/test/java/edu/kit/datamanager/ro_crate/examples/ExamplesOfSpecificationV1p1Test.java @@ -18,13 +18,50 @@ */ public class ExamplesOfSpecificationV1p1Test { + /** + * From: + * Minimal Example + * + *

+ * This is equivalent to {@link #testMinimalCrateWithoutCrateBuilder()}, but using more convenient APIs. + */ + @Test + void testMinimalCrateConvenient() { + // Example 1: Basic RO-Crate + RoCrate minimal = new RoCrate.RoCrateBuilder( + "Data files associated with the manuscript:Effects of facilitated family case conferencing for ...", + "Palliative care planning for nursing home residents with advanced dementia ...", + "2017", + "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + ) + .setLicense( new ContextualEntity.ContextualEntityBuilder() + .addType("CreativeWork") + .setId("https://creativecommons.org/licenses/by-nc-sa/3.0/au/") + .addProperty("description", "This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Australia License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/3.0/au/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.") + .addProperty("identifier", "https://creativecommons.org/licenses/by-nc-sa/3.0/au/") + .addProperty("name", "Attribution-NonCommercial-ShareAlike 3.0 Australia (CC BY-NC-SA 3.0 AU)") + .build() + ) + .addIdentifier("https://doi.org/10.4225/59/59672c09f4a4b") + .build(); + + // So you get something to see + prettyPrintJsonString(minimal.getJsonMetadata()); + // Compare with the example from the specification + try { + HelpFunctions.compareCrateJsonToFileInResources(minimal, "/spec-v1.1-example-json-files/minimal.json"); + } catch (IOException e) { + throw new AssertionFailedError("Missing resources file!", e); + } + } + /** * From: * Minimal Example * *

* In this example, the crate is created without the builder. - * For the following examples, more convenient methods are used. + * Otherwise, the example is the same as {@link #testMinimalCrateConvenient()}. */ @Test void testMinimalCrateWithoutCrateBuilder() {