From 998306685070480294427164b90d5fb1a1f6f0c5 Mon Sep 17 00:00:00 2001 From: Michael Schnell Date: Sun, 14 Jan 2024 15:59:20 +0100 Subject: [PATCH] Added new annotation to locate constant field --- api/pom.xml | 6 + ...ava => HasSerializedDataTypeConstant.java} | 12 +- ...asSerializedDataTypeConstantValidator.java | 58 +++++++++ ...rializedDataTypeConstantValidatorTest.java | 116 ++++++++++++++++++ 4 files changed, 191 insertions(+), 1 deletion(-) rename api/src/main/java/org/fuin/esc/api/{SerializedDataTypeConstant.java => HasSerializedDataTypeConstant.java} (56%) create mode 100644 api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidator.java create mode 100644 api/src/test/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidatorTest.java diff --git a/api/pom.xml b/api/pom.xml index 591e1d29..be94a16a 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -101,6 +101,12 @@ test + + org.glassfish + jakarta.el + test + + org.xmlunit xmlunit-core diff --git a/api/src/main/java/org/fuin/esc/api/SerializedDataTypeConstant.java b/api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstant.java similarity index 56% rename from api/src/main/java/org/fuin/esc/api/SerializedDataTypeConstant.java rename to api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstant.java index 96912b5a..6a4d1820 100644 --- a/api/src/main/java/org/fuin/esc/api/SerializedDataTypeConstant.java +++ b/api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstant.java @@ -1,5 +1,8 @@ package org.fuin.esc.api; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + import java.lang.annotation.*; /** @@ -10,7 +13,8 @@ @Target(ElementType.TYPE) @Inherited @Retention(RetentionPolicy.RUNTIME) -public @interface SerializedDataTypeConstant { +@Constraint(validatedBy = { HasSerializedDataTypeConstantValidator.class }) +public @interface HasSerializedDataTypeConstant { /** * Returns the name of a public static constant in the annotated class. @@ -19,4 +23,10 @@ */ String value() default "SER_TYPE"; + String message() default "Does not define a public static constant with the given name"; + + Class[] groups() default {}; + + Class[] payload() default {}; + } diff --git a/api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidator.java b/api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidator.java new file mode 100644 index 00000000..a7ee446e --- /dev/null +++ b/api/src/main/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidator.java @@ -0,0 +1,58 @@ +package org.fuin.esc.api; + +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +/** + * Determines if the annotated class has a public static constant with the given name and {@link SerializedDataType} type. + */ +public class HasSerializedDataTypeConstantValidator implements ConstraintValidator { + + private String name; + + @Override + public void initialize(HasSerializedDataTypeConstant annotation) { + this.name = annotation.value(); + } + + @Override + public boolean isValid(Object obj, ConstraintValidatorContext context) { + try { + final Field field = obj.getClass().getField(name); + final int modifiers = field.getModifiers(); + if (!Modifier.isStatic(modifiers)) { + error(context, "Field '" + name + "' is not static (#1)"); + return false; + } + if (field.getType() != SerializedDataType.class) { + error(context, "Expected constant '" + name + "' to be of type '" + SerializedDataType.class.getName() + "', but was: " + field.getType().getName() + " (#3)"); + return false; + } + final Object value = field.get(obj); + if (value == null) { + error(context, "Constant '" + name + "' is expected to be a non-null value (#4)"); + return false; + } + if (!Modifier.isFinal(modifiers)) { + error(context, "Constant '" + name + "' is not not final (#5)"); + return false; + } + return true; + } catch (final NoSuchFieldException ex) { + error(context, "The field '" + name + "' is undefined or it is not public (#2)"); + return false; + } catch (final IllegalAccessException ex) { + throw new IllegalStateException("Failed to execute method", ex); + } + + } + + private void error(ConstraintValidatorContext context, String message) { + context.disableDefaultConstraintViolation(); + context.buildConstraintViolationWithTemplate(message).addConstraintViolation(); + } + +} diff --git a/api/src/test/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidatorTest.java b/api/src/test/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidatorTest.java new file mode 100644 index 00000000..191b1dfd --- /dev/null +++ b/api/src/test/java/org/fuin/esc/api/HasSerializedDataTypeConstantValidatorTest.java @@ -0,0 +1,116 @@ +/** + * Copyright (C) 2013 Future Invent Informationsmanagement GmbH. All rights + * reserved. + *

+ * This library is free software; you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation; either version 3 of the License, or (at your option) any + * later version. + *

+ * This library is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more + * details. + *

+ * You should have received a copy of the GNU Lesser General Public License + * along with this library. If not, see . + */ +package org.fuin.esc.api; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThat; + +public final class HasSerializedDataTypeConstantValidatorTest { + + private static Validator validator; + + @BeforeAll + static void beforeAll() { + try (final ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory()) { + validator = validatorFactory.getValidator(); + } + } + + @Test + public final void testValid() { + assertThat(validator.validate(new MyClassValid())).isEmpty(); + } + + @Test + public final void testNotStatic() { + assertThat(first(validator.validate(new MyClassNotStatic()))).contains("#1"); + } + + @Test + public final void testNotPublic() { + assertThat(first(validator.validate(new MyClassNotPublic()))).contains("#2"); + } + + @Test + public final void testWrongReturnType() { + assertThat(first(validator.validate(new MyClassWrongType()))).contains("#3"); + } + + @Test + public final void testWrongReturn() { + assertThat(first(validator.validate(new MyClassNullValue()))).contains("#4"); + } + + @Test + public final void testNoMethod() { + assertThat(first(validator.validate(new MyClassNoField()))).contains("#2"); + } + + @Test + public final void testNotFinal() { + assertThat(first(validator.validate(new MyClassNotFinal()))).contains("#5"); + } + + private static String first(Set violations) { + return violations.stream().map(v -> ((ConstraintViolation) v).getMessage()).findFirst().orElse(null); + } + + @HasSerializedDataTypeConstant + public static final class MyClassValid { + public static final SerializedDataType SER_TYPE = new SerializedDataType("XYZ"); + } + + @HasSerializedDataTypeConstant + public static final class MyClassNotStatic { + public final SerializedDataType SER_TYPE = new SerializedDataType("XYZ"); + } + + @HasSerializedDataTypeConstant + public static final class MyClassNotPublic { + protected static final SerializedDataType SER_TYPE = new SerializedDataType("XYZ"); + } + + @HasSerializedDataTypeConstant + public static final class MyClassNoField { + } + + @HasSerializedDataTypeConstant + public static final class MyClassWrongType { + public static final Integer SER_TYPE = 123; + } + + @HasSerializedDataTypeConstant + public static final class MyClassNullValue { + public static final SerializedDataType SER_TYPE = null; + } + + @HasSerializedDataTypeConstant + public static final class MyClassNotFinal { + public static SerializedDataType SER_TYPE = new SerializedDataType("XYZ"); + } + + +}