From ac251913e13032fbcc814568c1c362a1b57d57b3 Mon Sep 17 00:00:00 2001 From: Martin Ledvinka Date: Tue, 7 Feb 2023 16:55:56 +0100 Subject: [PATCH] [Enhancement #43] Properly handle term mapping conflicts in contexts. --- .../ContextBuildingJsonLdSerializer.java | 13 ++++++++++ .../context/DummyTermMappingHolder.java | 5 ++++ .../context/EmbeddedTermMappingHolder.java | 10 +++++++- .../context/MappingJsonLdContext.java | 4 ++-- .../context/TermMappingHolder.java | 2 ++ .../WriteThroughTermMappingHolder.java | 5 ++++ .../EmbeddedTermMappingHolderTest.java | 24 +++++++++++++++++++ 7 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 src/test/java/cz/cvut/kbss/jsonld/serialization/context/EmbeddedTermMappingHolderTest.java diff --git a/src/main/java/cz/cvut/kbss/jsonld/serialization/ContextBuildingJsonLdSerializer.java b/src/main/java/cz/cvut/kbss/jsonld/serialization/ContextBuildingJsonLdSerializer.java index d9a14fc..3ab34f6 100644 --- a/src/main/java/cz/cvut/kbss/jsonld/serialization/ContextBuildingJsonLdSerializer.java +++ b/src/main/java/cz/cvut/kbss/jsonld/serialization/ContextBuildingJsonLdSerializer.java @@ -4,10 +4,12 @@ import cz.cvut.kbss.jsonld.ConfigParam; import cz.cvut.kbss.jsonld.Configuration; import cz.cvut.kbss.jsonld.JsonLd; +import cz.cvut.kbss.jsonld.exception.AmbiguousTermMappingException; import cz.cvut.kbss.jsonld.serialization.context.JsonLdContext; import cz.cvut.kbss.jsonld.serialization.context.JsonLdContextFactory; import cz.cvut.kbss.jsonld.serialization.context.MappingJsonLdContextFactory; import cz.cvut.kbss.jsonld.serialization.model.CollectionNode; +import cz.cvut.kbss.jsonld.serialization.model.CompositeNode; import cz.cvut.kbss.jsonld.serialization.model.JsonNode; import cz.cvut.kbss.jsonld.serialization.model.ObjectNode; import cz.cvut.kbss.jsonld.serialization.serializer.LiteralValueSerializers; @@ -24,6 +26,7 @@ import java.time.*; import java.util.Collection; import java.util.Date; +import java.util.Optional; /** * JSON-LD serializer outputting compacted JSON-LD with context. @@ -77,10 +80,20 @@ protected JsonNode buildJsonTree(Object root) { final JsonLdTreeBuilder treeBuilder = initTreeBuilder(traverser, jsonLdContextFactory); traverser.setVisitor(treeBuilder); traverser.traverse(root); + ensureContextNodeNotPresent(treeBuilder.getTreeRoot(), rootContext.getContextNode()); treeBuilder.getTreeRoot().prependItem(rootContext.getContextNode()); return treeBuilder.getTreeRoot(); } + private void ensureContextNodeNotPresent(CompositeNode root, JsonNode rootCtx) { + final Optional ctxNode = + root.getItems().stream().filter(n -> JsonLd.CONTEXT.equals(n.getName())).findAny(); + if (ctxNode.isPresent()) { + throw new AmbiguousTermMappingException( + "Unable to build context hierarchy. Attempted to add two root contexts. Original root context: " + rootCtx + ", conflicting: " + ctxNode.get()); + } + } + private JsonLdTreeBuilder initTreeBuilder(ObjectGraphTraverser traverser, JsonLdContextFactory jsonLdContextFactory) { return new JsonLdTreeBuilder(new ObjectGraphValueSerializers(serializers, diff --git a/src/main/java/cz/cvut/kbss/jsonld/serialization/context/DummyTermMappingHolder.java b/src/main/java/cz/cvut/kbss/jsonld/serialization/context/DummyTermMappingHolder.java index 79f486d..d92497b 100644 --- a/src/main/java/cz/cvut/kbss/jsonld/serialization/context/DummyTermMappingHolder.java +++ b/src/main/java/cz/cvut/kbss/jsonld/serialization/context/DummyTermMappingHolder.java @@ -48,4 +48,9 @@ boolean hasTermMapping(String term, JsonNode mappedNode) { boolean isEmpty() { return true; } + + @Override + boolean isRoot() { + return true; + } } diff --git a/src/main/java/cz/cvut/kbss/jsonld/serialization/context/EmbeddedTermMappingHolder.java b/src/main/java/cz/cvut/kbss/jsonld/serialization/context/EmbeddedTermMappingHolder.java index 714db92..30a903e 100644 --- a/src/main/java/cz/cvut/kbss/jsonld/serialization/context/EmbeddedTermMappingHolder.java +++ b/src/main/java/cz/cvut/kbss/jsonld/serialization/context/EmbeddedTermMappingHolder.java @@ -1,5 +1,6 @@ package cz.cvut.kbss.jsonld.serialization.context; +import cz.cvut.kbss.jsonld.exception.AmbiguousTermMappingException; import cz.cvut.kbss.jsonld.serialization.model.JsonNode; import java.util.*; @@ -28,14 +29,21 @@ void registerTermMapping(String term, JsonNode mappedNode) { if (!isRoot() && !parentContext.hasTermMapping(term)) { parentContext.registerTermMapping(term, mappedNode); } else { + verifyMappingUnique(term, mappedNode); mapping.put(term, mappedNode); } } - private boolean isRoot() { + boolean isRoot() { return parentContext == DummyTermMappingHolder.INSTANCE; } + private void verifyMappingUnique(String term, JsonNode value) { + if (mapping.containsKey(term) && !Objects.equals(mapping.get(term), value)) { + throw new AmbiguousTermMappingException("Context already contains mapping for term '" + term + "'."); + } + } + @Override public Map getMapping() { return Collections.unmodifiableMap(mapping); diff --git a/src/main/java/cz/cvut/kbss/jsonld/serialization/context/MappingJsonLdContext.java b/src/main/java/cz/cvut/kbss/jsonld/serialization/context/MappingJsonLdContext.java index e95adce..350aafe 100644 --- a/src/main/java/cz/cvut/kbss/jsonld/serialization/context/MappingJsonLdContext.java +++ b/src/main/java/cz/cvut/kbss/jsonld/serialization/context/MappingJsonLdContext.java @@ -31,7 +31,7 @@ public MappingJsonLdContext(JsonLdContext parent) { @Override public void registerTermMapping(String term, String iri) { final JsonNode value = JsonNodeFactory.createStringLiteralNode(term, iri); - if (!mappingHolder.canRegisterTermMapping(term, value)) { + if (!mappingHolder.canRegisterTermMapping(term, value) && !mappingHolder.isRoot()) { this.mappingHolder = new EmbeddedTermMappingHolder(mappingHolder); } mappingHolder.registerTermMapping(term, value); @@ -39,7 +39,7 @@ public void registerTermMapping(String term, String iri) { @Override public void registerTermMapping(String term, ObjectNode mappedNode) { - if (!mappingHolder.canRegisterTermMapping(term, mappedNode)) { + if (!mappingHolder.canRegisterTermMapping(term, mappedNode) && !mappingHolder.isRoot()) { this.mappingHolder = new EmbeddedTermMappingHolder(mappingHolder); } mappingHolder.registerTermMapping(term, mappedNode); diff --git a/src/main/java/cz/cvut/kbss/jsonld/serialization/context/TermMappingHolder.java b/src/main/java/cz/cvut/kbss/jsonld/serialization/context/TermMappingHolder.java index e432eec..dbbe259 100644 --- a/src/main/java/cz/cvut/kbss/jsonld/serialization/context/TermMappingHolder.java +++ b/src/main/java/cz/cvut/kbss/jsonld/serialization/context/TermMappingHolder.java @@ -31,6 +31,8 @@ abstract class TermMappingHolder { abstract boolean isEmpty(); + abstract boolean isRoot(); + public Optional getMappedTerm(String iri) { Objects.requireNonNull(iri); for (Map.Entry e : getMapping().entrySet()) { diff --git a/src/main/java/cz/cvut/kbss/jsonld/serialization/context/WriteThroughTermMappingHolder.java b/src/main/java/cz/cvut/kbss/jsonld/serialization/context/WriteThroughTermMappingHolder.java index 99e15cc..dcbf2bf 100644 --- a/src/main/java/cz/cvut/kbss/jsonld/serialization/context/WriteThroughTermMappingHolder.java +++ b/src/main/java/cz/cvut/kbss/jsonld/serialization/context/WriteThroughTermMappingHolder.java @@ -46,4 +46,9 @@ Map getMapping() { boolean isEmpty() { return true; } + + @Override + boolean isRoot() { + return false; + } } diff --git a/src/test/java/cz/cvut/kbss/jsonld/serialization/context/EmbeddedTermMappingHolderTest.java b/src/test/java/cz/cvut/kbss/jsonld/serialization/context/EmbeddedTermMappingHolderTest.java new file mode 100644 index 0000000..546e9ef --- /dev/null +++ b/src/test/java/cz/cvut/kbss/jsonld/serialization/context/EmbeddedTermMappingHolderTest.java @@ -0,0 +1,24 @@ +package cz.cvut.kbss.jsonld.serialization.context; + +import cz.cvut.kbss.jopa.vocabulary.DC; +import cz.cvut.kbss.jopa.vocabulary.RDFS; +import cz.cvut.kbss.jsonld.exception.AmbiguousTermMappingException; +import cz.cvut.kbss.jsonld.serialization.JsonNodeFactory; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class EmbeddedTermMappingHolderTest { + + private final EmbeddedTermMappingHolder sut = new EmbeddedTermMappingHolder(); + + @Test + void registerTermMappingThrowsAmbiguousTermMappingExceptionWhenRootHolderAlreadyContainsTermMapping() { + final String term = "name"; + sut.registerTermMapping(term, JsonNodeFactory.createStringLiteralNode(term, RDFS.LABEL)); + assertThrows(AmbiguousTermMappingException.class, + () -> sut.registerTermMapping(term, + JsonNodeFactory.createStringLiteralNode(term, DC.Terms.TITLE))); + } + +} \ No newline at end of file