diff --git a/dynamic-relations/src/main/java/at/drm/util/DynamicRelationsUtils.java b/dynamic-relations/src/main/java/at/drm/util/DynamicRelationsUtils.java new file mode 100644 index 0000000..31001db --- /dev/null +++ b/dynamic-relations/src/main/java/at/drm/util/DynamicRelationsUtils.java @@ -0,0 +1,52 @@ +package at.drm.util; + +import at.drm.dao.RelationDao; +import at.drm.factory.RelationDaoFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.core.ResolvableType; +import org.springframework.stereotype.Component; +import java.lang.reflect.Field; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class DynamicRelationsUtils { + + private final RelationDaoFactory relationDaoFactory; + + public List> listRegisteredEntities() { + Set allDaos = relationDaoFactory.getAllDaos(); + + return allDaos.stream() + .map(this::extractEntityClassFromDao) + .distinct() + .collect(Collectors.toList()); + } + + private Class extractEntityClassFromDao(RelationDao relationDao) { + try { + ResolvableType resolvableType = ResolvableType.forClass(relationDao.getClass()).as(RelationDao.class); + ResolvableType generic = resolvableType.getGeneric(0); + Class relationLinkClass = getRelationLinkClass(generic); + + Field sourceObjectField = relationLinkClass.getDeclaredField("sourceObject"); + return sourceObjectField.getType(); + } catch (NoSuchFieldException e) { + throw new RuntimeException( + "Could not find sourceObject field in RelationLink class for DAO: " + relationDao.getClass(), e + ); + } + } + + private Class getRelationLinkClass(ResolvableType generic) { + Class relationLinkClass = generic.resolve(); + + if (relationLinkClass == null) { + throw new RuntimeException("Could not resolve RelationLink class for type: " + generic.getClass()); + } + + return relationLinkClass; + } +} diff --git a/dynamic-relations/src/test/java/at/drm/testutil/daos/AnotherTestEntityRelationDao.java b/dynamic-relations/src/test/java/at/drm/testutil/daos/AnotherTestEntityRelationDao.java new file mode 100644 index 0000000..fef704d --- /dev/null +++ b/dynamic-relations/src/test/java/at/drm/testutil/daos/AnotherTestEntityRelationDao.java @@ -0,0 +1,6 @@ +package at.drm.testutil.daos; + +import at.drm.dao.RelationDao; +import at.drm.testutil.relations.AnotherTestEntityRelationLink; + +public abstract class AnotherTestEntityRelationDao implements RelationDao { } diff --git a/dynamic-relations/src/test/java/at/drm/testutil/daos/InvalidRelationDao.java b/dynamic-relations/src/test/java/at/drm/testutil/daos/InvalidRelationDao.java new file mode 100644 index 0000000..28aa175 --- /dev/null +++ b/dynamic-relations/src/test/java/at/drm/testutil/daos/InvalidRelationDao.java @@ -0,0 +1,6 @@ +package at.drm.testutil.daos; + +import at.drm.dao.RelationDao; +import at.drm.testutil.relations.InvalidRelationLink; + +public abstract class InvalidRelationDao implements RelationDao { } diff --git a/dynamic-relations/src/test/java/at/drm/testutil/daos/TestEntityRelationDao.java b/dynamic-relations/src/test/java/at/drm/testutil/daos/TestEntityRelationDao.java new file mode 100644 index 0000000..723fb60 --- /dev/null +++ b/dynamic-relations/src/test/java/at/drm/testutil/daos/TestEntityRelationDao.java @@ -0,0 +1,6 @@ +package at.drm.testutil.daos; + +import at.drm.dao.RelationDao; +import at.drm.testutil.relations.TestEntityRelationLink; + +public abstract class TestEntityRelationDao implements RelationDao { } diff --git a/dynamic-relations/src/test/java/at/drm/testutil/entities/AnotherTestEntity.java b/dynamic-relations/src/test/java/at/drm/testutil/entities/AnotherTestEntity.java new file mode 100644 index 0000000..d73db9f --- /dev/null +++ b/dynamic-relations/src/test/java/at/drm/testutil/entities/AnotherTestEntity.java @@ -0,0 +1,17 @@ +package at.drm.testutil.entities; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Data +@AllArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString(onlyExplicitlyIncluded = true) +public class AnotherTestEntity { + + @EqualsAndHashCode.Include + @ToString.Include + private Long id; +} diff --git a/dynamic-relations/src/test/java/at/drm/testutil/entities/SomeTestEntity.java b/dynamic-relations/src/test/java/at/drm/testutil/entities/SomeTestEntity.java new file mode 100644 index 0000000..e273db2 --- /dev/null +++ b/dynamic-relations/src/test/java/at/drm/testutil/entities/SomeTestEntity.java @@ -0,0 +1,17 @@ +package at.drm.testutil.entities; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@Data +@AllArgsConstructor +@EqualsAndHashCode(onlyExplicitlyIncluded = true) +@ToString(onlyExplicitlyIncluded = true) +public class SomeTestEntity { + + @EqualsAndHashCode.Include + @ToString.Include + private Long id; +} diff --git a/dynamic-relations/src/test/java/at/drm/testutil/relations/AnotherTestEntityRelationLink.java b/dynamic-relations/src/test/java/at/drm/testutil/relations/AnotherTestEntityRelationLink.java new file mode 100644 index 0000000..b90251c --- /dev/null +++ b/dynamic-relations/src/test/java/at/drm/testutil/relations/AnotherTestEntityRelationLink.java @@ -0,0 +1,16 @@ +package at.drm.testutil.relations; + +import at.drm.model.RelationLink; +import at.drm.testutil.entities.AnotherTestEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AnotherTestEntityRelationLink implements RelationLink { + private AnotherTestEntity sourceObject; + private Long targetId; + private String targetType; +} diff --git a/dynamic-relations/src/test/java/at/drm/testutil/relations/InvalidRelationLink.java b/dynamic-relations/src/test/java/at/drm/testutil/relations/InvalidRelationLink.java new file mode 100644 index 0000000..37f87df --- /dev/null +++ b/dynamic-relations/src/test/java/at/drm/testutil/relations/InvalidRelationLink.java @@ -0,0 +1,26 @@ +package at.drm.testutil.relations; + +import at.drm.model.RelationLink; +import at.drm.testutil.entities.SomeTestEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class InvalidRelationLink implements RelationLink { + private SomeTestEntity wrongFieldName; + private Long targetId; + private String targetType; + + @Override + public SomeTestEntity getSourceObject() { + return wrongFieldName; + } + + @Override + public void setSourceObject(SomeTestEntity sourceObject) { + this.wrongFieldName = sourceObject; + } +} diff --git a/dynamic-relations/src/test/java/at/drm/testutil/relations/TestEntityRelationLink.java b/dynamic-relations/src/test/java/at/drm/testutil/relations/TestEntityRelationLink.java new file mode 100644 index 0000000..b066371 --- /dev/null +++ b/dynamic-relations/src/test/java/at/drm/testutil/relations/TestEntityRelationLink.java @@ -0,0 +1,16 @@ +package at.drm.testutil.relations; + +import at.drm.model.RelationLink; +import at.drm.testutil.entities.SomeTestEntity; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class TestEntityRelationLink implements RelationLink { + private SomeTestEntity sourceObject; + private Long targetId; + private String targetType; +} diff --git a/dynamic-relations/src/test/java/at/drm/util/DynamicRelationsUtilsTest.java b/dynamic-relations/src/test/java/at/drm/util/DynamicRelationsUtilsTest.java new file mode 100644 index 0000000..a3902a2 --- /dev/null +++ b/dynamic-relations/src/test/java/at/drm/util/DynamicRelationsUtilsTest.java @@ -0,0 +1,131 @@ +package at.drm.util; + +import at.drm.factory.RelationDaoFactory; +import at.drm.testutil.daos.AnotherTestEntityRelationDao; +import at.drm.testutil.daos.InvalidRelationDao; +import at.drm.testutil.daos.TestEntityRelationDao; +import at.drm.testutil.entities.AnotherTestEntity; +import at.drm.testutil.entities.SomeTestEntity; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class DynamicRelationsUtilsTest { + + @Mock + private RelationDaoFactory relationDaoFactory; + + private DynamicRelationsUtils dynamicRelationsUtils; + + @BeforeEach + void setUp() { + dynamicRelationsUtils = new DynamicRelationsUtils(relationDaoFactory); + } + + @Test + void testListRegisteredEntities_WithMultipleDaos_ShouldReturnDistinctEntityClasses() { + TestEntityRelationDao dao1 = mock(TestEntityRelationDao.class); + AnotherTestEntityRelationDao dao2 = mock(AnotherTestEntityRelationDao.class); + + when(relationDaoFactory.getAllDaos()).thenReturn(Set.of(dao1, dao2)); + + List> result = dynamicRelationsUtils.listRegisteredEntities(); + + assertNotNull(result); + assertEquals(2, result.size()); + assertTrue(result.contains(SomeTestEntity.class)); + assertTrue(result.contains(AnotherTestEntity.class)); + verify(relationDaoFactory, times(1)).getAllDaos(); + } + + @Test + void testListRegisteredEntities_WithDuplicateEntityTypes_ShouldReturnDistinctClasses() { + TestEntityRelationDao dao1 = mock(TestEntityRelationDao.class); + TestEntityRelationDao dao2 = mock(TestEntityRelationDao.class); + + when(relationDaoFactory.getAllDaos()).thenReturn(Set.of(dao1, dao2)); + + List> result = dynamicRelationsUtils.listRegisteredEntities(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(SomeTestEntity.class, result.getFirst()); + verify(relationDaoFactory, times(1)).getAllDaos(); + } + + @Test + void testListRegisteredEntities_WithEmptyDaoSet_ShouldReturnEmptyList() { + when(relationDaoFactory.getAllDaos()).thenReturn(Set.of()); + + List> result = dynamicRelationsUtils.listRegisteredEntities(); + + assertNotNull(result); + assertTrue(result.isEmpty()); + verify(relationDaoFactory, times(1)).getAllDaos(); + } + + @Test + void testExtractEntityClassFromDao_WithValidDao_ShouldReturnCorrectEntityClass() { + TestEntityRelationDao dao = mock(TestEntityRelationDao.class); + when(relationDaoFactory.getAllDaos()).thenReturn(Set.of(dao)); + + List> result = dynamicRelationsUtils.listRegisteredEntities(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(SomeTestEntity.class, result.getFirst()); + } + + @Test + void testExtractEntityClassFromDao_WithInvalidRelationLink_ShouldThrowRuntimeException() { + InvalidRelationDao invalidDao = mock(InvalidRelationDao.class); + when(relationDaoFactory.getAllDaos()).thenReturn(Set.of(invalidDao)); + + RuntimeException exception = assertThrows(RuntimeException.class, () -> dynamicRelationsUtils.listRegisteredEntities()); + + assertTrue(exception.getMessage().contains("Could not find sourceObject field")); + assertTrue(exception.getMessage().contains("InvalidRelationDao")); + verify(relationDaoFactory, times(1)).getAllDaos(); + } + + @Test + void testListRegisteredEntities_WithSingleDao_ShouldReturnSingleEntity() { + TestEntityRelationDao dao = mock(TestEntityRelationDao.class); + when(relationDaoFactory.getAllDaos()).thenReturn(Set.of(dao)); + + List> result = dynamicRelationsUtils.listRegisteredEntities(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(SomeTestEntity.class, result.getFirst()); + verify(relationDaoFactory, times(1)).getAllDaos(); + } + + @Test + void testListRegisteredEntities_VerifyStreamProcessing() { + TestEntityRelationDao dao1 = mock(TestEntityRelationDao.class); + AnotherTestEntityRelationDao dao2 = mock(AnotherTestEntityRelationDao.class); + TestEntityRelationDao dao3 = mock(TestEntityRelationDao.class); + + when(relationDaoFactory.getAllDaos()).thenReturn(Set.of(dao1, dao2, dao3)); + + List> result = dynamicRelationsUtils.listRegisteredEntities(); + + assertNotNull(result); + assertEquals(2, result.size()); + assertTrue(result.contains(SomeTestEntity.class)); + assertTrue(result.contains(AnotherTestEntity.class)); + + assertInstanceOf(List.class, result); + verify(relationDaoFactory, times(1)).getAllDaos(); + } +} diff --git a/testing/src/test/java/at/test/drm/ApplicationIntegrationTest.java b/testing/src/test/java/at/test/drm/ApplicationIntegrationTest.java index 27489f2..f06e449 100644 --- a/testing/src/test/java/at/test/drm/ApplicationIntegrationTest.java +++ b/testing/src/test/java/at/test/drm/ApplicationIntegrationTest.java @@ -1,5 +1,11 @@ package at.test.drm; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import at.drm.dao.RelationDao; +import at.drm.factory.RelationDaoFactory; +import at.drm.util.DynamicRelationsUtils; import java.util.List; import java.util.Set; @@ -32,6 +38,10 @@ class ApplicationIntegrationTest { private RelationService relationService; @Autowired private DynamicRelationsPrintService dynamicRelationsPrintService; + @Autowired + private DynamicRelationsUtils dynamicRelationsUtils; + @Autowired + private RelationDaoFactory relationDaoFactory; @Test void shouldFindRelationBySourceObject() { @@ -165,4 +175,15 @@ void shouldPrintRelationsWithCyclicRelations() { DocumentEntityType """); } + + @Test + void testListRegisteredEntities_shouldReturnRegisteredEntities() { + List> result = dynamicRelationsUtils.listRegisteredEntities(); + assertThat(result).hasSize(3); + assertThat(result).containsExactlyInAnyOrder( + PersonEntity.class, + DogEntity.class, + DocumentEntity.class + ); + } }