From e195db570877594a1b8445e1f75aefc729fe694f Mon Sep 17 00:00:00 2001 From: Thomas Turrell-Croft Date: Wed, 29 Mar 2023 11:10:54 +0100 Subject: [PATCH 1/5] Add LanguageMap validator --- .../xapi/model/ActivityDefinition.java | 6 +- .../dev/learning/xapi/model/Attachment.java | 3 + .../xapi/model/InteractionComponent.java | 2 + .../dev/learning/xapi/model/Statement.java | 4 +- .../java/dev/learning/xapi/model/Verb.java | 2 + .../constraints/ValidLanguageMap.java | 47 +++++++ .../validators/LanguageMapValidator.java | 57 ++++++++ .../jakarta.validation.ConstraintValidator | 1 + .../validators/LanguageMapValidatorTests.java | 131 ++++++++++++++++++ 9 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 xapi-model/src/main/java/dev/learning/xapi/model/validation/constraints/ValidLanguageMap.java create mode 100644 xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidator.java create mode 100644 xapi-model/src/test/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidatorTests.java diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/ActivityDefinition.java b/xapi-model/src/main/java/dev/learning/xapi/model/ActivityDefinition.java index c20ef990..58d40d0c 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/ActivityDefinition.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/ActivityDefinition.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import dev.learning.xapi.model.validation.constraints.HasScheme; +import dev.learning.xapi.model.validation.constraints.ValidLanguageMap; import java.net.URI; import java.util.ArrayList; import java.util.List; @@ -33,11 +34,13 @@ public class ActivityDefinition { /** * The human readable/visual name of the Activity. */ + @ValidLanguageMap private LanguageMap name; /** * A description of the Activity. */ + @ValidLanguageMap private LanguageMap description; /** @@ -92,7 +95,8 @@ public class ActivityDefinition { */ private Map<@HasScheme URI, Object> extensions; - // **Warning** do not add fields that are not required by the xAPI specification. + // **Warning** do not add fields that are not required by the xAPI + // specification. /** * Builder for ActivityDefinition. diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/Attachment.java b/xapi-model/src/main/java/dev/learning/xapi/model/Attachment.java index f40d697c..72f5ea4a 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/Attachment.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/Attachment.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import dev.learning.xapi.model.validation.constraints.ValidLanguageMap; import jakarta.validation.constraints.NotNull; import java.net.URI; import java.nio.charset.StandardCharsets; @@ -41,11 +42,13 @@ public class Attachment { * Display name of this Attachment. */ @NotNull + @ValidLanguageMap private LanguageMap display; /** * A description of the Attachment. */ + @ValidLanguageMap private LanguageMap description; /** diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/InteractionComponent.java b/xapi-model/src/main/java/dev/learning/xapi/model/InteractionComponent.java index b42bc529..8bdba77f 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/InteractionComponent.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/InteractionComponent.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import dev.learning.xapi.model.validation.constraints.ValidLanguageMap; import jakarta.validation.constraints.NotNull; import java.util.Locale; import lombok.Builder; @@ -36,6 +37,7 @@ public class InteractionComponent { /** * A description of the interaction component. */ + @ValidLanguageMap private LanguageMap description; // **Warning** do not add fields that are not required by the xAPI specification. diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/Statement.java b/xapi-model/src/main/java/dev/learning/xapi/model/Statement.java index 06fc81d9..4d6d5e78 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/Statement.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/Statement.java @@ -100,6 +100,7 @@ public class Statement implements CoreStatement { /** * Agent or Group who is asserting this Statement is true. */ + @Valid @ValidActor @ValidAuthority private Actor authority; @@ -113,6 +114,7 @@ public class Statement implements CoreStatement { /** * Headers for Attachments to the Statement. */ + @Valid @JsonFormat(without = {JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY}) private List attachments; @@ -349,7 +351,7 @@ public Builder addAttachment(Attachment attachment) { */ public Builder addAttachment(Consumer attachment) { - final Attachment.Builder builder = Attachment.builder(); + final var builder = Attachment.builder(); attachment.accept(builder); diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/Verb.java b/xapi-model/src/main/java/dev/learning/xapi/model/Verb.java index c90fd87c..00d8144e 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/Verb.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/Verb.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import dev.learning.xapi.model.validation.constraints.HasScheme; +import dev.learning.xapi.model.validation.constraints.ValidLanguageMap; import jakarta.validation.constraints.NotNull; import java.net.URI; import java.util.Locale; @@ -351,6 +352,7 @@ public class Verb { * impact on the meaning of the Statement, but serves to give a human-readable display of the * meaning already determined by the chosen Verb. */ + @ValidLanguageMap private LanguageMap display; // **Warning** do not add fields that are not required by the xAPI specification. diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/validation/constraints/ValidLanguageMap.java b/xapi-model/src/main/java/dev/learning/xapi/model/validation/constraints/ValidLanguageMap.java new file mode 100644 index 00000000..7d78e4c4 --- /dev/null +++ b/xapi-model/src/main/java/dev/learning/xapi/model/validation/constraints/ValidLanguageMap.java @@ -0,0 +1,47 @@ +/* + * Copyright 2016-2023 Berry Cloud Ltd. All rights reserved. + */ + +package dev.learning.xapi.model.validation.constraints; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.CONSTRUCTOR; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; +import static java.lang.annotation.ElementType.TYPE_USE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import jakarta.validation.Constraint; +import jakarta.validation.Payload; +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * All of the keys in the annotated LanguageMap must have a ISO3 Language and Country. + * + * @author István Rátkai (Selindek) + */ +@Documented +@Constraint(validatedBy = {}) +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) +@Retention(RUNTIME) +public @interface ValidLanguageMap { + + /** + * Error Message. + */ + String message() default "all keys must have a ISO3 Language and Country"; + + /** + * Groups. + */ + Class[] groups() default {}; + + /** + * Payload. + */ + Class[] payload() default {}; + +} diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidator.java b/xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidator.java new file mode 100644 index 00000000..35ae7c9c --- /dev/null +++ b/xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidator.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016-2023 Berry Cloud Ltd. All rights reserved. + */ + +package dev.learning.xapi.model.validation.internal.validators; + +import dev.learning.xapi.model.LanguageMap; +import dev.learning.xapi.model.validation.constraints.ValidLanguageMap; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import java.util.Locale; +import java.util.MissingResourceException; + +/** + * The Locale being validated must have a ISO3 Language and Country. + * + * @author István Rátkai (Selindek) + * @author Thomas Turrell-Croft + */ +public class LanguageMapValidator implements ConstraintValidator { + + @Override + public boolean isValid(LanguageMap value, ConstraintValidatorContext context) { + + if (value == null) { + return true; + } + + return value.keySet().stream().allMatch(this::valid); + + } + + private boolean valid(Locale locale) { + + try { + locale.getISO3Language(); + locale.getISO3Country(); + + return true; + } catch (final MissingResourceException e1) { + + // Handle locale instantiated with Locale#Locale(String) + final var blar = Locale.forLanguageTag(locale.toString()); + + try { + blar.getISO3Language(); + blar.getISO3Country(); + + return true; + } catch (final MissingResourceException e2) { + + return false; + } + } + } + +} diff --git a/xapi-model/src/main/resources/META-INF/services/jakarta.validation.ConstraintValidator b/xapi-model/src/main/resources/META-INF/services/jakarta.validation.ConstraintValidator index 77c1c051..16fe9940 100644 --- a/xapi-model/src/main/resources/META-INF/services/jakarta.validation.ConstraintValidator +++ b/xapi-model/src/main/resources/META-INF/services/jakarta.validation.ConstraintValidator @@ -10,3 +10,4 @@ dev.learning.xapi.model.validation.internal.validators.StatementRevisionValidato dev.learning.xapi.model.validation.internal.validators.StatementPlatformValidator dev.learning.xapi.model.validation.internal.validators.StatementVerbValidator dev.learning.xapi.model.validation.internal.validators.StatementsValidator +dev.learning.xapi.model.validation.internal.validators.LanguageMapValidator diff --git a/xapi-model/src/test/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidatorTests.java b/xapi-model/src/test/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidatorTests.java new file mode 100644 index 00000000..9bab15f6 --- /dev/null +++ b/xapi-model/src/test/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidatorTests.java @@ -0,0 +1,131 @@ +/* + * Copyright 2016-2023 Berry Cloud Ltd. All rights reserved. + */ + +package dev.learning.xapi.model.validation.internal.validators; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dev.learning.xapi.model.LanguageMap; +import java.util.Locale; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * ScaledScoreValidator Tests. + * + * @author István Rátkai (Selindek) + */ +@DisplayName("ScaledSoreValidator tests") +class LanguageMapValidatorTests { + + private static final LanguageMapValidator validator = new LanguageMapValidator(); + + @Test + void whenValueIsNullThenResultIsTrue() { + + // When Value Is Null + final var result = validator.isValid(null, null); + + // Then Result Is True + assertTrue(result); + } + + @Test + void whenCallingIsValidOnLanguageMapWithUKKeyThenResultIsTrue() { + + final var languageMap = new LanguageMap(); + languageMap.put(Locale.UK, "Hello World!"); + + // When Calling Is Valid On Language Map With UK Key + final var result = validator.isValid(languageMap, null); + + // Then Result Is True + assertTrue(result); + } + + @Test + void whenCallingIsValidOnLanguageMapWithENKeyThenResultIsTrue() { + + final var languageMap = new LanguageMap(); + languageMap.put(Locale.ENGLISH, "Hello World!"); + + // When Calling Is Valid On Language Map With EN Key + final var result = validator.isValid(languageMap, null); + + // Then Result Is True + assertTrue(result); + } + + @Test + void whenCallingIsValidOnLanguageMapWithENAndUKKeysThenResultIsTrue() { + + final var languageMap = new LanguageMap(); + languageMap.put(Locale.ENGLISH, "Hello World!"); + languageMap.put(Locale.UK, "Hello World!"); + + // When Calling Is Valid On Language Map With EN And UK Keys + final var result = validator.isValid(languageMap, null); + + // Then Result Is True + assertTrue(result); + } + + @Test + void whenCallingIsValidOnLanguageMapWithENAndUnknownKeysThenResultIsFalse() { + + final var languageMap = new LanguageMap(); + languageMap.put(Locale.ENGLISH, "Hello World!"); + languageMap.put(Locale.forLanguageTag("unknown"), "Hello World!"); + + // When Calling Is Valid On Language Map With EN And Unknown Keys + final var result = validator.isValid(languageMap, null); + + // Then Result Is False + assertFalse(result); + } + + @Test + void whenCallingIsValidOnLanguageMapWithChineseSimplifiedKeyUsingForLangugeTagThenResultIsTrue() { + + final var languageMap = new LanguageMap(); + languageMap.put(Locale.forLanguageTag("zh-CHS"), "Hello World!"); + + // When Calling Is Valid On Language Map With Chinese Simplified Key + final var result = validator.isValid(languageMap, null); + + // Then Result Is True + assertTrue(result); + } + + @ParameterizedTest + @ValueSource(strings = {"und", "zh-CHS", "zh-CN", "zh-Hans", "zh-Hant", "zh-HK"}) + void whenCallingIsValidOnLanguageMapWithValidKeyThenResultIsTrue(String arg) { + + final var languageMap = new LanguageMap(); + languageMap.put(new Locale(arg), "Hello World!"); + + // When Calling Is Valid On Language Map With Valid Key + final var result = validator.isValid(languageMap, null); + + // Then Result Is True + assertTrue(result); + } + + @Test + void whenCallingIsValidOnLanguageMapWithUnknownKeyThenResultIsFalse() { + + final var languageMap = new LanguageMap(); + languageMap.put(new Locale("unknown"), "Hello World!"); + + // When Calling Is Valid On Language Map With Unknown Key + final var result = validator.isValid(languageMap, null); + + // Then Result Is False + assertFalse(result); + } + +} From 58022ce5102b76e673a02c1193c3fa6cc56edf2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20R=C3=A1tkai?= Date: Thu, 30 Mar 2023 15:09:48 +0100 Subject: [PATCH 2/5] add locale validator instead of language-map validator --- .../xapiserver/StatementsControllerTest.java | 2 +- .../xapi/model/ActivityDefinition.java | 6 +- .../dev/learning/xapi/model/Attachment.java | 9 +- .../java/dev/learning/xapi/model/Context.java | 2 + .../xapi/model/InteractionComponent.java | 4 +- .../java/dev/learning/xapi/model/Verb.java | 4 +- ...ValidLanguageMap.java => ValidLocale.java} | 4 +- .../validators/LanguageMapValueExtractor.java | 27 ++++ ...MapValidator.java => LocaleValidator.java} | 15 +- .../jakarta.validation.ConstraintValidator | 2 +- ....validation.valueextraction.ValueExtractor | 1 + .../validators/LanguageMapValidatorTests.java | 131 ------------------ .../validators/LocaleValidatorTests.java | 107 ++++++++++++++ 13 files changed, 157 insertions(+), 157 deletions(-) rename xapi-model/src/main/java/dev/learning/xapi/model/validation/constraints/{ValidLanguageMap.java => ValidLocale.java} (90%) create mode 100644 xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValueExtractor.java rename xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/{LanguageMapValidator.java => LocaleValidator.java} (69%) create mode 100644 xapi-model/src/main/resources/META-INF/services/jakarta.validation.valueextraction.ValueExtractor delete mode 100644 xapi-model/src/test/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidatorTests.java create mode 100644 xapi-model/src/test/java/dev/learning/xapi/model/validation/internal/validators/LocaleValidatorTests.java diff --git a/samples/xapi-server/src/test/java/dev/learning/xapi/samples/xapiserver/StatementsControllerTest.java b/samples/xapi-server/src/test/java/dev/learning/xapi/samples/xapiserver/StatementsControllerTest.java index 29644e5b..88640ece 100644 --- a/samples/xapi-server/src/test/java/dev/learning/xapi/samples/xapiserver/StatementsControllerTest.java +++ b/samples/xapi-server/src/test/java/dev/learning/xapi/samples/xapiserver/StatementsControllerTest.java @@ -35,7 +35,7 @@ void whenPuttingStatementThenStatusIsNoContent() throws Exception { // When Putting Statement mvc.perform(put("/xapi/statements").content( - "{\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"http://adlnet.gov/expapi/verbs/attempted\",\"display\":{\"und\":\"attempted\"}},\"object\":{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}}}") + "{\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"http://adlnet.gov/expapi/verbs/attempted\",\"display\":{\"ssund\":\"attempted\"}},\"object\":{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}}}") .contentType(MediaType.APPLICATION_JSON_VALUE) .param("statementId", "04417d92-2d51-4789-92b0-62b0a1b0127b")) diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/ActivityDefinition.java b/xapi-model/src/main/java/dev/learning/xapi/model/ActivityDefinition.java index 58d40d0c..a2f38ba5 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/ActivityDefinition.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/ActivityDefinition.java @@ -7,7 +7,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import dev.learning.xapi.model.validation.constraints.HasScheme; -import dev.learning.xapi.model.validation.constraints.ValidLanguageMap; +import dev.learning.xapi.model.validation.constraints.ValidLocale; import java.net.URI; import java.util.ArrayList; import java.util.List; @@ -34,13 +34,13 @@ public class ActivityDefinition { /** * The human readable/visual name of the Activity. */ - @ValidLanguageMap + @ValidLocale private LanguageMap name; /** * A description of the Activity. */ - @ValidLanguageMap + @ValidLocale private LanguageMap description; /** diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/Attachment.java b/xapi-model/src/main/java/dev/learning/xapi/model/Attachment.java index 72f5ea4a..6ed363bc 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/Attachment.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/Attachment.java @@ -7,8 +7,9 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; -import dev.learning.xapi.model.validation.constraints.ValidLanguageMap; +import dev.learning.xapi.model.validation.constraints.ValidLocale; import jakarta.validation.constraints.NotNull; +import jakarta.validation.valueextraction.Unwrapping; import java.net.URI; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -41,14 +42,14 @@ public class Attachment { /** * Display name of this Attachment. */ - @NotNull - @ValidLanguageMap + @NotNull(payload = Unwrapping.Skip.class) + @ValidLocale private LanguageMap display; /** * A description of the Attachment. */ - @ValidLanguageMap + @ValidLocale private LanguageMap description; /** diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/Context.java b/xapi-model/src/main/java/dev/learning/xapi/model/Context.java index 2be3c0aa..7b4e9cce 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/Context.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/Context.java @@ -11,6 +11,7 @@ import dev.learning.xapi.model.validation.constraints.HasScheme; import dev.learning.xapi.model.validation.constraints.NotUndetermined; import dev.learning.xapi.model.validation.constraints.ValidActor; +import dev.learning.xapi.model.validation.constraints.ValidLocale; import dev.learning.xapi.model.validation.constraints.Variant; import jakarta.validation.Valid; import java.net.URI; @@ -76,6 +77,7 @@ public class Context { */ @NotUndetermined @JsonSerialize(using = LocaleSerializer.class) + @ValidLocale private Locale language; /** diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/InteractionComponent.java b/xapi-model/src/main/java/dev/learning/xapi/model/InteractionComponent.java index 8bdba77f..26d7b30e 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/InteractionComponent.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/InteractionComponent.java @@ -6,7 +6,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; -import dev.learning.xapi.model.validation.constraints.ValidLanguageMap; +import dev.learning.xapi.model.validation.constraints.ValidLocale; import jakarta.validation.constraints.NotNull; import java.util.Locale; import lombok.Builder; @@ -37,7 +37,7 @@ public class InteractionComponent { /** * A description of the interaction component. */ - @ValidLanguageMap + @ValidLocale private LanguageMap description; // **Warning** do not add fields that are not required by the xAPI specification. diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/Verb.java b/xapi-model/src/main/java/dev/learning/xapi/model/Verb.java index 00d8144e..b1a209e9 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/Verb.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/Verb.java @@ -8,7 +8,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; import dev.learning.xapi.model.validation.constraints.HasScheme; -import dev.learning.xapi.model.validation.constraints.ValidLanguageMap; +import dev.learning.xapi.model.validation.constraints.ValidLocale; import jakarta.validation.constraints.NotNull; import java.net.URI; import java.util.Locale; @@ -352,7 +352,7 @@ public class Verb { * impact on the meaning of the Statement, but serves to give a human-readable display of the * meaning already determined by the chosen Verb. */ - @ValidLanguageMap + @ValidLocale private LanguageMap display; // **Warning** do not add fields that are not required by the xAPI specification. diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/validation/constraints/ValidLanguageMap.java b/xapi-model/src/main/java/dev/learning/xapi/model/validation/constraints/ValidLocale.java similarity index 90% rename from xapi-model/src/main/java/dev/learning/xapi/model/validation/constraints/ValidLanguageMap.java rename to xapi-model/src/main/java/dev/learning/xapi/model/validation/constraints/ValidLocale.java index 7d78e4c4..3154280c 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/validation/constraints/ValidLanguageMap.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/validation/constraints/ValidLocale.java @@ -19,7 +19,7 @@ import java.lang.annotation.Target; /** - * All of the keys in the annotated LanguageMap must have a ISO3 Language and Country. + * The annotated Locale must have a ISO3 Language and Country. * * @author István Rátkai (Selindek) */ @@ -27,7 +27,7 @@ @Constraint(validatedBy = {}) @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) @Retention(RUNTIME) -public @interface ValidLanguageMap { +public @interface ValidLocale { /** * Error Message. diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValueExtractor.java b/xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValueExtractor.java new file mode 100644 index 00000000..a27ddc5f --- /dev/null +++ b/xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValueExtractor.java @@ -0,0 +1,27 @@ +/* + * Copyright 2016-2023 Berry Cloud Ltd. All rights reserved. + */ + +package dev.learning.xapi.model.validation.internal.validators; + +import dev.learning.xapi.model.LanguageMap; +import jakarta.validation.valueextraction.ExtractedValue; +import jakarta.validation.valueextraction.UnwrapByDefault; +import jakarta.validation.valueextraction.ValueExtractor; +import java.util.Locale; + +/** + * ValueExtractor for {@link LanguageMap}. + * + * @author István Rátkai (Selindek) + */ +@UnwrapByDefault +public class LanguageMapValueExtractor + implements ValueExtractor<@ExtractedValue(type = Locale.class) LanguageMap> { + + @Override + public void extractValues(LanguageMap originalValue, ValueReceiver receiver) { + originalValue.keySet().forEach(k -> receiver.iterableValue(k.toLanguageTag(), k)); + } + +} diff --git a/xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidator.java b/xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/LocaleValidator.java similarity index 69% rename from xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidator.java rename to xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/LocaleValidator.java index 35ae7c9c..85b5dcd0 100644 --- a/xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidator.java +++ b/xapi-model/src/main/java/dev/learning/xapi/model/validation/internal/validators/LocaleValidator.java @@ -4,8 +4,7 @@ package dev.learning.xapi.model.validation.internal.validators; -import dev.learning.xapi.model.LanguageMap; -import dev.learning.xapi.model.validation.constraints.ValidLanguageMap; +import dev.learning.xapi.model.validation.constraints.ValidLocale; import jakarta.validation.ConstraintValidator; import jakarta.validation.ConstraintValidatorContext; import java.util.Locale; @@ -17,21 +16,15 @@ * @author István Rátkai (Selindek) * @author Thomas Turrell-Croft */ -public class LanguageMapValidator implements ConstraintValidator { +public class LocaleValidator implements ConstraintValidator { @Override - public boolean isValid(LanguageMap value, ConstraintValidatorContext context) { + public boolean isValid(Locale locale, ConstraintValidatorContext context) { - if (value == null) { + if (locale == null) { return true; } - return value.keySet().stream().allMatch(this::valid); - - } - - private boolean valid(Locale locale) { - try { locale.getISO3Language(); locale.getISO3Country(); diff --git a/xapi-model/src/main/resources/META-INF/services/jakarta.validation.ConstraintValidator b/xapi-model/src/main/resources/META-INF/services/jakarta.validation.ConstraintValidator index 16fe9940..0a70e80b 100644 --- a/xapi-model/src/main/resources/META-INF/services/jakarta.validation.ConstraintValidator +++ b/xapi-model/src/main/resources/META-INF/services/jakarta.validation.ConstraintValidator @@ -10,4 +10,4 @@ dev.learning.xapi.model.validation.internal.validators.StatementRevisionValidato dev.learning.xapi.model.validation.internal.validators.StatementPlatformValidator dev.learning.xapi.model.validation.internal.validators.StatementVerbValidator dev.learning.xapi.model.validation.internal.validators.StatementsValidator -dev.learning.xapi.model.validation.internal.validators.LanguageMapValidator +dev.learning.xapi.model.validation.internal.validators.LocaleValidator diff --git a/xapi-model/src/main/resources/META-INF/services/jakarta.validation.valueextraction.ValueExtractor b/xapi-model/src/main/resources/META-INF/services/jakarta.validation.valueextraction.ValueExtractor new file mode 100644 index 00000000..1ad0af7d --- /dev/null +++ b/xapi-model/src/main/resources/META-INF/services/jakarta.validation.valueextraction.ValueExtractor @@ -0,0 +1 @@ +dev.learning.xapi.model.validation.internal.validators.LanguageMapValueExtractor diff --git a/xapi-model/src/test/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidatorTests.java b/xapi-model/src/test/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidatorTests.java deleted file mode 100644 index 9bab15f6..00000000 --- a/xapi-model/src/test/java/dev/learning/xapi/model/validation/internal/validators/LanguageMapValidatorTests.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2016-2023 Berry Cloud Ltd. All rights reserved. - */ - -package dev.learning.xapi.model.validation.internal.validators; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import dev.learning.xapi.model.LanguageMap; -import java.util.Locale; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -/** - * ScaledScoreValidator Tests. - * - * @author István Rátkai (Selindek) - */ -@DisplayName("ScaledSoreValidator tests") -class LanguageMapValidatorTests { - - private static final LanguageMapValidator validator = new LanguageMapValidator(); - - @Test - void whenValueIsNullThenResultIsTrue() { - - // When Value Is Null - final var result = validator.isValid(null, null); - - // Then Result Is True - assertTrue(result); - } - - @Test - void whenCallingIsValidOnLanguageMapWithUKKeyThenResultIsTrue() { - - final var languageMap = new LanguageMap(); - languageMap.put(Locale.UK, "Hello World!"); - - // When Calling Is Valid On Language Map With UK Key - final var result = validator.isValid(languageMap, null); - - // Then Result Is True - assertTrue(result); - } - - @Test - void whenCallingIsValidOnLanguageMapWithENKeyThenResultIsTrue() { - - final var languageMap = new LanguageMap(); - languageMap.put(Locale.ENGLISH, "Hello World!"); - - // When Calling Is Valid On Language Map With EN Key - final var result = validator.isValid(languageMap, null); - - // Then Result Is True - assertTrue(result); - } - - @Test - void whenCallingIsValidOnLanguageMapWithENAndUKKeysThenResultIsTrue() { - - final var languageMap = new LanguageMap(); - languageMap.put(Locale.ENGLISH, "Hello World!"); - languageMap.put(Locale.UK, "Hello World!"); - - // When Calling Is Valid On Language Map With EN And UK Keys - final var result = validator.isValid(languageMap, null); - - // Then Result Is True - assertTrue(result); - } - - @Test - void whenCallingIsValidOnLanguageMapWithENAndUnknownKeysThenResultIsFalse() { - - final var languageMap = new LanguageMap(); - languageMap.put(Locale.ENGLISH, "Hello World!"); - languageMap.put(Locale.forLanguageTag("unknown"), "Hello World!"); - - // When Calling Is Valid On Language Map With EN And Unknown Keys - final var result = validator.isValid(languageMap, null); - - // Then Result Is False - assertFalse(result); - } - - @Test - void whenCallingIsValidOnLanguageMapWithChineseSimplifiedKeyUsingForLangugeTagThenResultIsTrue() { - - final var languageMap = new LanguageMap(); - languageMap.put(Locale.forLanguageTag("zh-CHS"), "Hello World!"); - - // When Calling Is Valid On Language Map With Chinese Simplified Key - final var result = validator.isValid(languageMap, null); - - // Then Result Is True - assertTrue(result); - } - - @ParameterizedTest - @ValueSource(strings = {"und", "zh-CHS", "zh-CN", "zh-Hans", "zh-Hant", "zh-HK"}) - void whenCallingIsValidOnLanguageMapWithValidKeyThenResultIsTrue(String arg) { - - final var languageMap = new LanguageMap(); - languageMap.put(new Locale(arg), "Hello World!"); - - // When Calling Is Valid On Language Map With Valid Key - final var result = validator.isValid(languageMap, null); - - // Then Result Is True - assertTrue(result); - } - - @Test - void whenCallingIsValidOnLanguageMapWithUnknownKeyThenResultIsFalse() { - - final var languageMap = new LanguageMap(); - languageMap.put(new Locale("unknown"), "Hello World!"); - - // When Calling Is Valid On Language Map With Unknown Key - final var result = validator.isValid(languageMap, null); - - // Then Result Is False - assertFalse(result); - } - -} diff --git a/xapi-model/src/test/java/dev/learning/xapi/model/validation/internal/validators/LocaleValidatorTests.java b/xapi-model/src/test/java/dev/learning/xapi/model/validation/internal/validators/LocaleValidatorTests.java new file mode 100644 index 00000000..31dc80a7 --- /dev/null +++ b/xapi-model/src/test/java/dev/learning/xapi/model/validation/internal/validators/LocaleValidatorTests.java @@ -0,0 +1,107 @@ +/* + * Copyright 2016-2023 Berry Cloud Ltd. All rights reserved. + */ + +package dev.learning.xapi.model.validation.internal.validators; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Locale; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +/** + * LocaleValidator Tests. + * + * @author István Rátkai (Selindek) + */ +@DisplayName("LocaleValidator tests") +class LocaleValidatorTests { + + private static final LocaleValidator validator = new LocaleValidator(); + + @Test + void whenValueIsNullThenResultIsTrue() { + + // When Value Is Null + final var result = validator.isValid(null, null); + + // Then Result Is True + assertTrue(result); + } + + @Test + void whenCallingIsValidOnLocaleWithUKKeyThenResultIsTrue() { + + // When Calling Is Valid On Locale With UK Key + final var result = validator.isValid(Locale.UK, null); + + // Then Result Is True + assertTrue(result); + } + + @Test + void whenCallingIsValidOnLocaleWithENKeyThenResultIsTrue() { + + // When Calling Is Valid On Locale With EN Key + final var result = validator.isValid(Locale.ENGLISH, null); + + // Then Result Is True + assertTrue(result); + } + + @Test + void whenCallingIsValidOnLocaleWithENAndUKKeysThenResultIsTrue() { + + // When Calling Is Valid On Locale With EN And UK Keys + final var result = validator.isValid(Locale.UK, null); + + // Then Result Is True + assertTrue(result); + } + + @Test + void whenCallingIsValidOnLocaleWithENAndUnknownKeysThenResultIsFalse() { + + // When Calling Is Valid On Locale With EN And Unknown Keys + final var result = validator.isValid(Locale.forLanguageTag("unknown"), null); + + // Then Result Is False + assertFalse(result); + } + + @Test + void whenCallingIsValidOnLocaleWithChineseSimplifiedKeyUsingForLangugeTagThenResultIsTrue() { + + // When Calling Is Valid On Locale With Chinese Simplified Key + final var result = validator.isValid(Locale.forLanguageTag("zh-CHS"), null); + + // Then Result Is True + assertTrue(result); + } + + @ParameterizedTest + @ValueSource(strings = {"und", "zh-CHS", "zh-CN", "zh-Hans", "zh-Hant", "zh-HK"}) + void whenCallingIsValidOnLocaleWithValidKeyThenResultIsTrue(String arg) { + + // When Calling Is Valid On Locale With Valid Key + final var result = validator.isValid(new Locale(arg), null); + + // Then Result Is True + assertTrue(result); + } + + @Test + void whenCallingIsValidOnLocaleWithUnknownKeyThenResultIsFalse() { + + // When Calling Is Valid On Locale With Unknown Key + final var result = validator.isValid(new Locale("unknown"), null); + + // Then Result Is False + assertFalse(result); + } + +} From e6a2832e86bca5b3a94934c38e32bea8d72b7b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20R=C3=A1tkai?= Date: Thu, 30 Mar 2023 15:12:45 +0100 Subject: [PATCH 3/5] fixup --- .../xapi/samples/xapiserver/StatementsControllerTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/samples/xapi-server/src/test/java/dev/learning/xapi/samples/xapiserver/StatementsControllerTest.java b/samples/xapi-server/src/test/java/dev/learning/xapi/samples/xapiserver/StatementsControllerTest.java index 88640ece..6b831b36 100644 --- a/samples/xapi-server/src/test/java/dev/learning/xapi/samples/xapiserver/StatementsControllerTest.java +++ b/samples/xapi-server/src/test/java/dev/learning/xapi/samples/xapiserver/StatementsControllerTest.java @@ -7,6 +7,7 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -35,7 +36,7 @@ void whenPuttingStatementThenStatusIsNoContent() throws Exception { // When Putting Statement mvc.perform(put("/xapi/statements").content( - "{\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"http://adlnet.gov/expapi/verbs/attempted\",\"display\":{\"ssund\":\"attempted\"}},\"object\":{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}}}") + "{\"actor\":{\"objectType\":\"Agent\",\"name\":\"A N Other\",\"mbox\":\"mailto:another@example.com\"},\"verb\":{\"id\":\"http://adlnet.gov/expapi/verbs/attempted\",\"display\":{\"und\":\"attempted\"}},\"object\":{\"objectType\":\"Activity\",\"id\":\"https://example.com/activity/simplestatement\",\"definition\":{\"name\":{\"en\":\"Simple Statement\"}}}}") .contentType(MediaType.APPLICATION_JSON_VALUE) .param("statementId", "04417d92-2d51-4789-92b0-62b0a1b0127b")) From 89927c93aaf373a118bbbd15a91acdbe02f0d438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20R=C3=A1tkai?= Date: Thu, 30 Mar 2023 15:14:52 +0100 Subject: [PATCH 4/5] sss --- .../xapi/samples/xapiserver/StatementsControllerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/samples/xapi-server/src/test/java/dev/learning/xapi/samples/xapiserver/StatementsControllerTest.java b/samples/xapi-server/src/test/java/dev/learning/xapi/samples/xapiserver/StatementsControllerTest.java index 6b831b36..29644e5b 100644 --- a/samples/xapi-server/src/test/java/dev/learning/xapi/samples/xapiserver/StatementsControllerTest.java +++ b/samples/xapi-server/src/test/java/dev/learning/xapi/samples/xapiserver/StatementsControllerTest.java @@ -7,7 +7,6 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; From e44aaf7d4f7d4bbbe696719f987fa2a8a6fba985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20R=C3=A1tkai?= Date: Thu, 30 Mar 2023 16:35:03 +0100 Subject: [PATCH 5/5] add one more test --- .../dev/learning/xapi/model/VerbTests.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/xapi-model/src/test/java/dev/learning/xapi/model/VerbTests.java b/xapi-model/src/test/java/dev/learning/xapi/model/VerbTests.java index 2d0de8d7..a811f4b3 100644 --- a/xapi-model/src/test/java/dev/learning/xapi/model/VerbTests.java +++ b/xapi-model/src/test/java/dev/learning/xapi/model/VerbTests.java @@ -319,7 +319,7 @@ void whenValidatingVerbWithAllRequiredPropertiesThenConstraintViolationsSizeIsZe final var verb = Verb.builder().id("http://adlnet.gov/expapi/verbs/answered") .addDisplay(Locale.US, "answered").build(); - // When Validating Interaction Component With All Required Properties + // When Validating Verb with All Required Properties final Set> constraintViolations = validator.validate(verb); // Then ConstraintViolations Size Is Zero @@ -332,7 +332,21 @@ void whenValidatingVerbWithoutIdThenConstraintViolationsSizeIsOne() { final var verb = Verb.builder().addDisplay(Locale.US, "answered").build(); - // When Validating Interaction Component Without Id + // When Validating Verb Component Without Id + final Set> constraintViolations = validator.validate(verb); + + // Then ConstraintViolations Size Is One + assertThat(constraintViolations, hasSize(1)); + + } + + @Test + void whenValidatingVerbWithInvalidDisplayIdThenConstraintViolationsSizeIsOne() { + + final var verb = Verb.builder().id("http://adlnet.gov/expapi/verbs/asked") + .addDisplay(new Locale("unknown"), "answered").build(); + + // When Validating Verb With invalid Display final Set> constraintViolations = validator.validate(verb); // Then ConstraintViolations Size Is One