Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version=2.1.0-rc2
version=2.1.0-rc3
action.custom-1=install
action.custom-1.args=--configure-on-demand -w -x check clean publishToMavenLocal
action.custom-2=jacoco
Expand Down
2 changes: 2 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 @@ -375,12 +375,14 @@ public RoCrateBuilder addDescription(String description) {
* @return returns the builder for further usage.
*/
public RoCrateBuilder addDataEntity(DataEntity dataEntity) {
this.metadataContext.checkEntity(dataEntity);
this.payload.addDataEntity(dataEntity);
this.rootDataEntity.addToHasPart(dataEntity.getId());
return this;
}

public RoCrateBuilder addContextualEntity(ContextualEntity contextualEntity) {
this.metadataContext.checkEntity(contextualEntity);
this.payload.addContextualEntity(contextualEntity);
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.io.IOException;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Function;

import edu.kit.datamanager.ro_crate.special.IdentifierUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
Expand All @@ -38,14 +39,16 @@ public class RoCrateMetadataContext implements CrateMetadataContext {
protected final HashMap<String, String> other = new HashMap<>();

/**
* Default constructor for the creation of the default context.
* Default constructor for the creation of the v1.1 default context.
*/
public RoCrateMetadataContext() {
this.addToContextFromUrl(DEFAULT_CONTEXT);
}

/**
* Constructor for creating the context from a list of url.
* <p>
* Note: Does NOT contain the default context if not explicitly given!
*
* @param urls the url list with different context.
*/
Expand All @@ -55,6 +58,8 @@ public RoCrateMetadataContext(Collection<String> urls) {

/**
* Constructor for creating the context from a json object.
* <p>
* Note: Does NOT contain the default context if not explicitly given!
*
* @param context the Json object of the context.
*/
Expand Down Expand Up @@ -83,6 +88,28 @@ public RoCrateMetadataContext(JsonNode context) {
}
}

/**
* Converts the context into a JSON-LD representation.
* <p>
* The resulting JSON structure depends on the content:
* - If there's only one URL and no key-value pairs: {"@context": "url"}
* - If there are multiple URLs and/or key-value pairs: {"@context": ["url1", "url2", {"key1": "value1", "key2": "value2"}]}
* <p>
* Example output:
* <pre>
* {
* "@context": [
* "https://w3id.org/ro/crate/1.1/context",
* {
* "schema": "http://schema.org/",
* "rdfs": "http://www.w3.org/2000/01/rdf-schema#"
* }
* ]
* }
* </pre>
*
* @return an ObjectNode containing the JSON-LD context representation
*/
@Override
public ObjectNode getContextJsonEntity() {
ObjectMapper objectMapper = MyObjectMapper.getMapper();
Expand All @@ -106,6 +133,21 @@ public ObjectNode getContextJsonEntity() {
return finalNode;
}

/**
* Checks if the given entity is valid according to the context.
* <p>
* - full URLs in the @type and field names are considered valid without further checks.
* - The "@id" value is treated as a special case, where it refers to the entity's ID.
* - The "@json" type is a linked data built-in type and is always considered valid.
* - If a type or field name is not found in the context, it will print an error message and return false.
* - This method checks both the types in the @type array and the field names in the entity's properties.
* - Prefixes in the context are considered valid if they match the context keys.
* - Suffixes after a valid prefix are considered valid in any case. This is not perfect,
* but it would be hard to handle correctly.
*
* @param entity the entity to check
* @return true if the entity is valid, false otherwise
*/
@Override
public boolean checkEntity(AbstractEntity entity) {
ObjectMapper objectMapper = MyObjectMapper.getMapper();
Expand All @@ -117,6 +159,11 @@ public boolean checkEntity(AbstractEntity entity) {
entity.getProperties().path("@type"),
new TypeReference<>() {}
);

final Function<String, Boolean> isFail = checkMeStr -> this.contextMap.get(checkMeStr) == null
&& this.contextMap.keySet().stream()
.noneMatch(key -> checkMeStr.startsWith(key + ":"));

// check if the items in the array of types are present in the context
for (String s : types) {
// special cases:
Expand All @@ -134,7 +181,7 @@ public boolean checkEntity(AbstractEntity entity) {
continue;
}

if (this.contextMap.get(s) == null) {
if (isFail.apply(s)) {
System.err.println("type " + s + " is missing from the context!");
return false;
}
Expand All @@ -147,14 +194,21 @@ public boolean checkEntity(AbstractEntity entity) {
// full URLs are considered fine
continue;
}
if (this.contextMap.get(s) == null) {
if (isFail.apply(s)) {
System.err.println("attribute name " + s + " is missing from context;");
return false;
}
}
return true;
}

/**
* Adds a URL to the context.
* <p>
* It will try to fetch the context from the URL.
*
* @param url the URL to add
*/
@Override
public void addToContextFromUrl(String url) {
this.urls.add(url);
Expand Down Expand Up @@ -194,18 +248,31 @@ public void addToContextFromUrl(String url) {
}));
}

/**
* Adds a key-value pair to the context.
*
* @param key the key to add. It may be a prefix or a term.
* @param value the value to add
*/
@Override
public void addToContext(String key, String value) {
this.contextMap.put(key, value);
this.other.put(key, value);
}

/**
* @param key the key for the value to retrieve.
* @return the value of the key if it exists in the context, null otherwise.
*/
@Override
public String getValueOf(String key) {
return Optional.ofNullable(this.contextMap.get(key))
.orElseGet(() -> this.other.get(key));
}

/**
* @return the set of all keys in the context.
*/
@Override
public Set<String> getKeys() {
List<String> merged = new ArrayList<>();
Expand All @@ -214,6 +281,11 @@ public Set<String> getKeys() {
return Set.copyOf(merged);
}

/**
* @return a map of all key-value pairs in the context. Note that some pairs may come
* from URLs or a pair may not be available as a context was not successfully resolved
* from a URL.
*/
@Override
public Map<String, String> getPairs() {
Map<String, String> merged = new HashMap<>();
Expand All @@ -222,13 +294,18 @@ public Map<String, String> getPairs() {
return Map.copyOf(merged);
}


/**
* @param key the key to delete from the context.
*/
@Override
public void deleteValuePairFromContext(String key) {
this.contextMap.remove(key);
this.other.remove(key);
}

/**
* @param url the URL to delete from the context.
*/
@Override
public void deleteUrlFromContext(String url) {
this.urls.remove(url);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.fasterxml.jackson.databind.node.ObjectNode;

import edu.kit.datamanager.ro_crate.HelpFunctions;
import edu.kit.datamanager.ro_crate.RoCrate;
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;
Expand All @@ -20,7 +21,7 @@

import static org.junit.jupiter.api.Assertions.*;

public class ContextTest {
public class RoCrateMetadataContextTest {

RoCrateMetadataContext context;
RoCrateMetadataContext complexContext;
Expand All @@ -32,7 +33,7 @@ void initContext() throws IOException {

final String crateManifestPath = "/crates/extendedContextExample/ro-crate-metadata.json";
ObjectMapper objectMapper = MyObjectMapper.getMapper();
JsonNode jsonNode = objectMapper.readTree(ContextTest.class.getResourceAsStream(crateManifestPath));
JsonNode jsonNode = objectMapper.readTree(RoCrateMetadataContextTest.class.getResourceAsStream(crateManifestPath));
this.complexContext = new RoCrateMetadataContext(jsonNode.get("@context"));
}

Expand Down Expand Up @@ -98,15 +99,22 @@ void creationFromPairsJsonTest() {
var objectMapper = MyObjectMapper.getMapper();

ObjectNode rawContext = objectMapper.createObjectNode();
rawContext.put("house", "www.example.con/house");
rawContext.put("road", "www.example.con/road");
rawContext.put("house", "www.example.com/house");
rawContext.put("road", "www.example.com/road");

ObjectNode rawCrate = objectMapper.createObjectNode();
rawCrate.set("@context", rawContext);
RoCrateMetadataContext newContext = new RoCrateMetadataContext(rawContext);
assertNotNull(newContext);

HelpFunctions.compare(newContext.getContextJsonEntity(), rawCrate, true);

var entityWithTerms = new ContextualEntity.ContextualEntityBuilder()
.setId("dkfaj")
.addType("house")
.addType("road")
.build();
assertTrue(newContext.checkEntity(entityWithTerms));
}

@Test
Expand Down Expand Up @@ -278,4 +286,21 @@ void testReadPairs() {
// prove immutability
assertThrows(UnsupportedOperationException.class, () -> given.put("newKey", "newValue"));
}

@Test
void checkEntity_withDefinedPrefixedType_succeeds() throws IOException {
// assume we read a crate just for demonstration
RoCrate crate = new RoCrate();
// and we extend the context
RoCrateMetadataContext context = new RoCrateMetadataContext();
context.addToContext("rdfs", "https://www.w3.org/2000/01/rdf-schema#");
crate.setMetadataContext(context);
// then we use the new context
DataEntity entity = new DataEntity.DataEntityBuilder()
.addType("rdfs:Property")
.build();
crate.addDataEntity(entity);
// Then we expect this to work
assertTrue(context.checkEntity(entity));
}
}
Loading