diff --git a/xapi-model/pom.xml b/xapi-model/pom.xml
index d2b186e4..ccc8aead 100644
--- a/xapi-model/pom.xml
+++ b/xapi-model/pom.xml
@@ -54,7 +54,12 @@
+ * Upon receiving a Statement with an Activity Definition that differs from the one stored, an LRS + * SHOULD ... change the definition and SHOULD update the stored Activity Definition. + *
+ *+ * When two ActivityDefinitions are merged, the properties and lists are replaced and the maps are + * merged. + *
* * @author Thomas Turrell-Croft * * @see xAPI * Activity Definition + * @see LRS + * Requirements */ @Value @Builder @@ -33,11 +45,13 @@ public class ActivityDefinition { /** * The human readable/visual name of the Activity. */ + @JsonMerge private LanguageMap name; /** * A description of the Activity. */ + @JsonMerge private LanguageMap description; /** @@ -90,6 +104,7 @@ public class ActivityDefinition { /** * A map of other properties as needed. */ + @JsonMerge private Map<@HasScheme URI, Object> extensions; // **Warning** do not add fields that are not required by the xAPI diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/Actor.java b/xapi-model/src/main/java/dev/learning/xapi/model/Actor.java index 8d8d8bc7..d522bac3 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/Actor.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/Actor.java @@ -4,6 +4,7 @@ package dev.learning.xapi.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; @@ -39,6 +40,7 @@ @JsonSubTypes.Type(value = Agent.class, name = "Person"), @JsonSubTypes.Type(value = Group.class, name = "Group")}) @JsonInclude(Include.NON_EMPTY) +@JsonIgnoreProperties("objectType") public abstract class Actor implements StatementObject, SubStatementObject { /** diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/StatementObject.java b/xapi-model/src/main/java/dev/learning/xapi/model/StatementObject.java index f381c5ab..9a683898 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/StatementObject.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/StatementObject.java @@ -4,6 +4,7 @@ package dev.learning.xapi.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonSubTypes; @@ -26,6 +27,7 @@ @JsonSubTypes.Type(value = Group.class, name = "Group"), @JsonSubTypes.Type(value = SubStatement.class, name = "SubStatement"), @JsonSubTypes.Type(value = StatementReference.class, name = "StatementRef")}) +@JsonIgnoreProperties("objectType") public interface StatementObject { } diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/SubStatementObject.java b/xapi-model/src/main/java/dev/learning/xapi/model/SubStatementObject.java index 457b2ad2..8f46ad2f 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/SubStatementObject.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/SubStatementObject.java @@ -4,6 +4,7 @@ package dev.learning.xapi.model; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonSubTypes; @@ -28,6 +29,7 @@ @JsonSubTypes.Type(value = Agent.class, name = "Agent"), @JsonSubTypes.Type(value = Group.class, name = "Group"), @JsonSubTypes.Type(value = StatementReference.class, name = "StatementRef")}) +@JsonIgnoreProperties("objectType") public interface SubStatementObject { } diff --git a/xapi-model/src/test/java/dev/learning/xapi/model/ActivityDefinitionTests.java b/xapi-model/src/test/java/dev/learning/xapi/model/ActivityDefinitionTests.java index 83eade6d..490e2521 100644 --- a/xapi-model/src/test/java/dev/learning/xapi/model/ActivityDefinitionTests.java +++ b/xapi-model/src/test/java/dev/learning/xapi/model/ActivityDefinitionTests.java @@ -9,16 +9,21 @@ import static org.hamcrest.Matchers.aMapWithSize; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; +import static org.springframework.integration.test.matcher.MapContentMatchers.hasAllEntries; import com.fasterxml.jackson.databind.ObjectMapper; +import dev.learning.xapi.model.validation.constraints.HasScheme; import java.io.IOException; import java.net.URI; import java.util.Collections; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.util.ResourceUtils; + /** * Activity Definition Tests. * @@ -331,8 +336,7 @@ void whenSerializingActivityDefinitionOfInteractionTypeTrueFalseThenResultIsEqua .build(); // When Serializing Activity Definition Of InteractionType True False - final var result = - objectMapper.readTree(objectMapper.writeValueAsString(activityDefinition)); + final var result = objectMapper.readTree(objectMapper.writeValueAsString(activityDefinition)); // Then Result Is Equal To Expected Json assertThat(result, is(objectMapper @@ -367,8 +371,7 @@ void whenSerializingActivityDefinitionOfInteractionTypeChoiceThenResultIsEqualTo .build(); // When Serializing Activity Definition Of InteractionType Choice - final var result = - objectMapper.readTree(objectMapper.writeValueAsString(activityDefinition)); + final var result = objectMapper.readTree(objectMapper.writeValueAsString(activityDefinition)); // Then Result Is Equal To Expected Json assertThat(result, is( @@ -441,4 +444,112 @@ void whenBuildingActivityDefinitionWithTwoDescriptionValuesThenDescriptionLangua } + @Test + void whenMergingActivityDefinitionsWithNamesThenMergedNameIsExpected() throws IOException { + + final var activityDefinition1 = + ActivityDefinition.builder().addName(Locale.UK, "Colour").build(); + + final var x = + objectMapper.valueToTree(ActivityDefinition.builder().addName(Locale.US, "Color").build()); + + final var expected = new LanguageMap(); + expected.put(Locale.UK, "Colour"); + expected.put(Locale.US, "Color"); + + // When Merging ActivityDefinitions With Names + final var merged = + (ActivityDefinition) objectMapper.readerForUpdating(activityDefinition1).readValue(x); + + // Then Merged Name Is Expected + assertThat(merged.getName(), hasAllEntries(expected)); + + } + + @Test + void whenMergingActivityDefinitionsWithDescriptionsThenMergedDescriptionIsExpected() + throws IOException { + + final var activityDefinition1 = + ActivityDefinition.builder().addDescription(Locale.UK, "flavour").build(); + + final var x = objectMapper + .valueToTree(ActivityDefinition.builder().addDescription(Locale.US, "flavor").build()); + + final var expected = new LanguageMap(); + expected.put(Locale.UK, "flavour"); + expected.put(Locale.US, "flavor"); + + // When Merging ActivityDefinitions With Descriptions + final var merged = + (ActivityDefinition) objectMapper.readerForUpdating(activityDefinition1).readValue(x); + + // Then Merged Description Is Expected + assertThat(merged.getDescription(), hasAllEntries(expected)); + + } + + @Test + void whenMergingActivityDefinitionsWithExtensionsThenMergedExtensionsAreExpected() + throws IOException { + + final Map<@HasScheme URI, Object> extensions1 = new HashMap<>(); + extensions1.put(URI.create("https://example.com/extensions/1"), "1"); + + final var activityDefinition1 = ActivityDefinition.builder().addName(Locale.UK, "Colour") + .addDescription(Locale.UK, "flavour").extensions(extensions1).build(); + + final Map<@HasScheme URI, Object> extensions2 = new HashMap<>(); + extensions2.put(URI.create("https://example.com/extensions/2"), "2"); + + final var x = objectMapper.valueToTree(ActivityDefinition.builder().addName(Locale.US, "Color") + .addDescription(Locale.US, "flavor").extensions(extensions2).build()); + + final Map<@HasScheme URI, Object> expected = new HashMap<>(); + expected.put(URI.create("https://example.com/extensions/1"), "1"); + expected.put(URI.create("https://example.com/extensions/2"), "2"); + + // When Merging ActivityDefinitions With Extensions + final var merged = + (ActivityDefinition) objectMapper.readerForUpdating(activityDefinition1).readValue(x); + + // Then Merged Extensions Are Expected + assertThat(merged.getExtensions(), hasAllEntries(expected)); + + } + + @Test + void whenMergingActivityDefinitionsWithNestedExtensionsThenMergedExtensionsAreExpected() + throws IOException { + + final Map<@HasScheme URI, Object> extensions1 = new HashMap<>(); + extensions1.put(URI.create("https://example.com/extensions/map"), + new HashMap<>(Collections.singletonMap("a", "y"))); + + final var activityDefinition1 = ActivityDefinition.builder().extensions(extensions1).build(); + + final Map<@HasScheme URI, Object> extensions2 = new HashMap<>(); + extensions2.put(URI.create("https://example.com/extensions/map"), + new HashMap<>(Collections.singletonMap("b", "z"))); + + final var x = + objectMapper.valueToTree(ActivityDefinition.builder().extensions(extensions2).build()); + + final Map