Skip to content

Commit

Permalink
New annotation with validator
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-schnell committed Jan 14, 2024
1 parent 80ac048 commit 3e2d0a2
Show file tree
Hide file tree
Showing 15 changed files with 221 additions and 26 deletions.
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.1</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/org/fuin/ddd4j/ddd/AggregateRootUuid.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import jakarta.validation.constraints.NotNull;
import org.fuin.objects4j.common.ConstraintViolationException;
import org.fuin.objects4j.common.Contract;
import org.fuin.objects4j.common.IsValidCapable;
import org.fuin.objects4j.common.HasPublicStaticIsValidMethod;
import org.fuin.objects4j.vo.ValueObjectWithBaseType;

import java.util.UUID;
Expand All @@ -29,7 +29,7 @@
/**
* UUID based aggregate root identifier.
*/
@IsValidCapable
@HasPublicStaticIsValidMethod
public abstract class AggregateRootUuid implements AggregateRootId, Comparable<AggregateRootUuid>, ValueObjectWithBaseType<UUID> {

private static final long serialVersionUID = 1000L;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.fuin.ddd4j.ddd;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.*;

/**
Expand All @@ -10,7 +13,8 @@
@Target(ElementType.TYPE)
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface EntityIdTypeConstant {
@Constraint(validatedBy = { HasEntityTypeConstantValidator.class })
public @interface HasEntityTypeConstant {

/**
* Returns the name of a public static constant of type {@link EntityType} in the annotated class.
Expand All @@ -19,4 +23,10 @@
*/
String value() default "TYPE";

String message() default "Does not define a public static constant with the given name";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.fuin.ddd4j.ddd;

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 EntityType} type.
*/
public class HasEntityTypeConstantValidator implements ConstraintValidator<HasEntityTypeConstant, Object> {

private String name;

@Override
public void initialize(HasEntityTypeConstant 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() != EntityType.class) {
error(context, "Expected constant '" + name + "' to be of type '" + EntityType.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();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void testSerializeDeserialize() {

// PREPARE
final VendorId vendorId = new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119"));
final AggregateAlreadyExistsException original = new AggregateAlreadyExistsException(VendorId.ENTITY_TYPE, vendorId, 102);
final AggregateAlreadyExistsException original = new AggregateAlreadyExistsException(VendorId.TYPE, vendorId, 102);

// TEST
final byte[] data = serialize(original);
Expand All @@ -71,7 +71,7 @@ public void testHashCodeEquals() {
public final void testMarshalUnmarshalXML() throws Exception {

// PREPARE
final AggregateAlreadyExistsException original = new AggregateAlreadyExistsException(VendorId.ENTITY_TYPE,
final AggregateAlreadyExistsException original = new AggregateAlreadyExistsException(VendorId.TYPE,
new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119")), 102);

// TEST
Expand Down Expand Up @@ -105,7 +105,7 @@ public final void testMarshalUnmarshalXML() throws Exception {
public final void testMarshalUnmarshalJSON() throws Exception {

// PREPARE
final AggregateAlreadyExistsException original = new AggregateAlreadyExistsException(VendorId.ENTITY_TYPE,
final AggregateAlreadyExistsException original = new AggregateAlreadyExistsException(VendorId.TYPE,
new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119")), 102);

// TEST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void testSerializeDeserialize() {

// PREPARE
final VendorId vendorId = new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119"));
final AggregateDeletedException original = new AggregateDeletedException(VendorId.ENTITY_TYPE, vendorId);
final AggregateDeletedException original = new AggregateDeletedException(VendorId.TYPE, vendorId);

// TEST
final byte[] data = serialize(original);
Expand All @@ -70,7 +70,7 @@ public void testHashCodeEquals() {
public final void testMarshalUnmarshalXML() throws Exception {

// PREPARE
final AggregateDeletedException original = new AggregateDeletedException(VendorId.ENTITY_TYPE,
final AggregateDeletedException original = new AggregateDeletedException(VendorId.TYPE,
new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119")));

// TEST
Expand Down Expand Up @@ -102,7 +102,7 @@ public final void testMarshalUnmarshalXML() throws Exception {
public final void testMarshalUnmarshalJSON() throws Exception {

// PREPARE
final AggregateDeletedException original = new AggregateDeletedException(VendorId.ENTITY_TYPE,
final AggregateDeletedException original = new AggregateDeletedException(VendorId.TYPE,
new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119")));

// TEST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void testSerializeDeserialize() {

// PREPARE
final VendorId vendorId = new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119"));
final AggregateNotFoundException original = new AggregateNotFoundException(VendorId.ENTITY_TYPE, vendorId);
final AggregateNotFoundException original = new AggregateNotFoundException(VendorId.TYPE, vendorId);

// TEST
final byte[] data = serialize(original);
Expand All @@ -70,7 +70,7 @@ public void testHashCodeEquals() {
public final void testMarshalUnmarshalXML() throws Exception {

// PREPARE
final AggregateNotFoundException original = new AggregateNotFoundException(VendorId.ENTITY_TYPE,
final AggregateNotFoundException original = new AggregateNotFoundException(VendorId.TYPE,
new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119")));

// TEST
Expand Down Expand Up @@ -102,7 +102,7 @@ public final void testMarshalUnmarshalXML() throws Exception {
public final void testMarshalUnmarshalJSON() throws Exception {

// PREPARE
final AggregateNotFoundException original = new AggregateNotFoundException(VendorId.ENTITY_TYPE,
final AggregateNotFoundException original = new AggregateNotFoundException(VendorId.TYPE,
new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119")));

// TEST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void testSerializeDeserialize() {

// PREPARE
final VendorId vendorId = new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119"));
final AggregateVersionConflictException original = new AggregateVersionConflictException(VendorId.ENTITY_TYPE, vendorId, 47, 102);
final AggregateVersionConflictException original = new AggregateVersionConflictException(VendorId.TYPE, vendorId, 47, 102);

// TEST
final byte[] data = serialize(original);
Expand All @@ -72,7 +72,7 @@ public void testHashCodeEquals() {
public final void testMarshalUnmarshalXML() throws Exception {

// PREPARE
final AggregateVersionConflictException original = new AggregateVersionConflictException(VendorId.ENTITY_TYPE,
final AggregateVersionConflictException original = new AggregateVersionConflictException(VendorId.TYPE,
new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119")), 47, 102);

// TEST
Expand Down Expand Up @@ -107,7 +107,7 @@ public final void testMarshalUnmarshalXML() throws Exception {
public final void testMarshalUnmarshalJSON() throws Exception {

// PREPARE
final AggregateVersionConflictException original = new AggregateVersionConflictException(VendorId.ENTITY_TYPE,
final AggregateVersionConflictException original = new AggregateVersionConflictException(VendorId.TYPE,
new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119")), 47, 102);

// TEST
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void testSerializeDeserialize() {

// PREPARE
final VendorId vendorId = new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119"));
final AggregateVersionNotFoundException original = new AggregateVersionNotFoundException(VendorId.ENTITY_TYPE, vendorId, 47);
final AggregateVersionNotFoundException original = new AggregateVersionNotFoundException(VendorId.TYPE, vendorId, 47);

// TEST
final byte[] data = serialize(original);
Expand All @@ -71,7 +71,7 @@ public void testHashCodeEquals() {
public final void testMarshalUnmarshalXML() throws Exception {

// PREPARE
final AggregateVersionNotFoundException original = new AggregateVersionNotFoundException(VendorId.ENTITY_TYPE,
final AggregateVersionNotFoundException original = new AggregateVersionNotFoundException(VendorId.TYPE,
new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119")), 47);

// TEST
Expand Down Expand Up @@ -105,7 +105,7 @@ public final void testMarshalUnmarshalXML() throws Exception {
public final void testMarshalUnmarshalJSON() throws Exception {

// PREPARE
final AggregateVersionNotFoundException original = new AggregateVersionNotFoundException(VendorId.ENTITY_TYPE,
final AggregateVersionNotFoundException original = new AggregateVersionNotFoundException(VendorId.TYPE,
new VendorId(UUID.fromString("4dcf4c2c-10e1-4db9-ba9e-d1e644e9d119")), 47);

// TEST
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* Copyright (C) 2013 Future Invent Informationsmanagement GmbH. All rights
* reserved. <http://www.fuin.org/>
* <p>
* 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.
* <p>
* 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.
* <p>
* You should have received a copy of the GNU Lesser General Public License
* along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
package org.fuin.ddd4j.ddd;

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 HasEntityTypeConstantValidatorTest {

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);
}

@HasEntityTypeConstant
public static final class MyClassValid {
public static final EntityType TYPE = new StringBasedEntityType("XYZ");
}

@HasEntityTypeConstant
public static final class MyClassNotStatic {
public final EntityType TYPE = new StringBasedEntityType("XYZ");
}

@HasEntityTypeConstant
public static final class MyClassNotPublic {
protected static final EntityType TYPE = new StringBasedEntityType("XYZ");
}

@HasEntityTypeConstant
public static final class MyClassNoField {
}

@HasEntityTypeConstant
public static final class MyClassWrongType {
public static final Integer TYPE = 123;
}

@HasEntityTypeConstant
public static final class MyClassNullValue {
public static final EntityType TYPE = null;
}

@HasEntityTypeConstant
public static final class MyClassNotFinal {
public static EntityType TYPE = new StringBasedEntityType("XYZ");
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void addVendorKey(VendorKey key) throws DuplicateVendorKeyException {
repo.update(vendor);

// VERIFY
final AggregateStreamId streamId = new AggregateStreamId(VendorId.ENTITY_TYPE, "vendorId", vendorId);
final AggregateStreamId streamId = new AggregateStreamId(VendorId.TYPE, "vendorId", vendorId);
final StreamEventsSlice slice = eventStore.readEventsForward(streamId, 0, 100);
assertThat(slice.getEvents()).hasSize(1);
vendor = repo.read(vendorId, vendor.getVersion());
Expand Down Expand Up @@ -96,7 +96,7 @@ public void addVendorKey(VendorKey key) throws DuplicateVendorKeyException {
repo.update(vendor);

// VERIFY
final AggregateStreamId streamId = new AggregateStreamId(VendorId.ENTITY_TYPE, "vendorId", vendorId);
final AggregateStreamId streamId = new AggregateStreamId(VendorId.TYPE, "vendorId", vendorId);
final StreamEventsSlice slice = eventStore.readEventsForward(streamId, 0, 100);
assertThat(slice.getEvents()).hasSize(2);
vendor = repo.read(vendorId, vendor.getVersion());
Expand Down Expand Up @@ -135,7 +135,7 @@ public void addVendorKey(VendorKey key) throws DuplicateVendorKeyException {
repo.delete(vendorId, vendor.getVersion());

// VERIFY
final AggregateStreamId streamId = new AggregateStreamId(VendorId.ENTITY_TYPE, "vendorId", vendorId);
final AggregateStreamId streamId = new AggregateStreamId(VendorId.TYPE, "vendorId", vendorId);
assertThat(eventStore.streamExists(streamId)).isFalse();
try {
repo.read(vendorId);
Expand Down
Loading

0 comments on commit 3e2d0a2

Please sign in to comment.