diff --git a/src/main/java/org/zalando/baigan/annotation/ConfigurationServiceScan.java b/src/main/java/org/zalando/baigan/annotation/ConfigurationServiceScan.java index 646d5f1..c8469ec 100644 --- a/src/main/java/org/zalando/baigan/annotation/ConfigurationServiceScan.java +++ b/src/main/java/org/zalando/baigan/annotation/ConfigurationServiceScan.java @@ -42,14 +42,21 @@ /** * Alias for the {@link #basePackages()} attribute. Allows for more concise * annotation declarations e.g.: - * {@code @EnableConfigService("de.zalando.shop")} instead of - * {@code @EnableConfigService(basePackages= "de.zalando.shop"})}. + * {@code @ConfigurationServiceScan("de.zalando.shop")} instead of + * {@code @ConfigurationServiceScan(basePackages= "de.zalando.shop"})}. */ String[] value() default {}; /** - * Base packages to scan for AppConfigService interfaces. + * Base packages to scan for {@link BaiganConfig} interfaces. */ String[] basePackages() default {}; + /** + * Type-safe alternative to {@link #basePackages} for specifying the packages + * to scan for {@link BaiganConfig} interfaces. The package of each class + * specified will be scanned. + */ + Class[] basePackageClasses() default {}; + } \ No newline at end of file diff --git a/src/main/java/org/zalando/baigan/proxy/ConfigurationBeanDefinitionRegistrar.java b/src/main/java/org/zalando/baigan/proxy/ConfigurationBeanDefinitionRegistrar.java index 12e001e..6993fff 100644 --- a/src/main/java/org/zalando/baigan/proxy/ConfigurationBeanDefinitionRegistrar.java +++ b/src/main/java/org/zalando/baigan/proxy/ConfigurationBeanDefinitionRegistrar.java @@ -27,6 +27,7 @@ import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.filter.AnnotationTypeFilter; +import org.springframework.util.ClassUtils; import org.springframework.util.StringUtils; import org.zalando.baigan.annotation.BaiganConfig; import org.zalando.baigan.annotation.ConfigurationServiceScan; @@ -69,12 +70,17 @@ public void registerBeanDefinitions( final List basePackages = Lists.newArrayList(); basePackages.addAll( - Arrays.asList(annotationAttributes.getStringArray("value"))); - basePackages.addAll(Arrays - .asList(annotationAttributes.getStringArray("basePackages"))); + Arrays.asList(annotationAttributes.getStringArray("value")) + ); + basePackages.addAll( + Arrays.asList(annotationAttributes.getStringArray("basePackages")) + ); + for (Class clazz : annotationAttributes.getClassArray("basePackageClasses")) { + basePackages.add(ClassUtils.getPackageName(clazz)); + } final Set saneSet = basePackages.stream() - .filter(str -> !StringUtils.isEmpty(str)) + .filter(StringUtils::hasText) .collect(Collectors.toSet()); createAndRegisterBeanDefinitions(saneSet, registry); diff --git a/src/test/java/org/zalando/baigan/context/ConfigurationBeanDefinitionRegistrarTest.java b/src/test/java/org/zalando/baigan/context/ConfigurationBeanDefinitionRegistrarTest.java index 1279f4b..257abe5 100644 --- a/src/test/java/org/zalando/baigan/context/ConfigurationBeanDefinitionRegistrarTest.java +++ b/src/test/java/org/zalando/baigan/context/ConfigurationBeanDefinitionRegistrarTest.java @@ -19,82 +19,104 @@ import com.google.common.collect.ImmutableMap; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.core.type.AnnotationMetadata; -import org.zalando.baigan.annotation.BaiganConfig; import org.zalando.baigan.annotation.ConfigurationServiceScan; +import org.zalando.baigan.context.packagec.Marker; +import org.zalando.baigan.proxy.BaiganConfigClasses; import org.zalando.baigan.proxy.ConfigurationBeanDefinitionRegistrar; +import java.util.List; import java.util.Map; -import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * @author mchand */ public class ConfigurationBeanDefinitionRegistrarTest { - private final AnnotationMetadata metaData = Mockito.mock(AnnotationMetadata.class); - private final BeanDefinitionRegistry registry = Mockito.mock(BeanDefinitionRegistry.class); + private final AnnotationMetadata metaData = mock(AnnotationMetadata.class); + private final BeanDefinitionRegistry registry = mock(BeanDefinitionRegistry.class); private final ConfigurationBeanDefinitionRegistrar registrar = new ConfigurationBeanDefinitionRegistrar(); @Test - public void testRegistration() { - - Mockito.when(metaData.getAnnotationAttributes( - ConfigurationServiceScan.class.getName())) - .thenReturn(ImmutableMap.of("value", - new String[]{"org.zalando.baigan.context"}, - "basePackages", new String[]{})); - - final ArgumentCaptor beanDefinition = ArgumentCaptor - .forClass(AbstractBeanDefinition.class); - final ArgumentCaptor beanName = ArgumentCaptor - .forClass(String.class); + public void testRegistrationOfBeans() { + when(metaData.getAnnotationAttributes(ConfigurationServiceScan.class.getName())).thenReturn( + ImmutableMap.of( + "value", new String[]{"org.zalando.baigan.context.packagea"}, + "basePackages", new String[]{"org.zalando.baigan.context.packageb"}, + "basePackageClasses", new Class[]{Marker.class} + ) + ); + + final ArgumentCaptor beanName = ArgumentCaptor.forClass(String.class); registrar.registerBeanDefinitions(metaData, registry); - verify(registry, times(2)).registerBeanDefinition(beanName.capture(), - beanDefinition.capture()); + verify(registry, times(4)).registerBeanDefinition(beanName.capture(), any()); + + assertThat( + beanName.getAllValues(), + containsInAnyOrder( + "org.zalando.baigan.context.packagea.SuperSonicBaiganProxyConfigurationFactoryBean", + "org.zalando.baigan.context.packageb.UltraVioletBaiganProxyConfigurationFactoryBean", + "org.zalando.baigan.context.packagec.LiquidCrystalBaiganProxyConfigurationFactoryBean", + "baiganConfigClasses" + ) + ); + } - assertThat(beanName.getAllValues(), contains( - "org.zalando.baigan.context.SuperSonicBaiganProxyConfigurationFactoryBean", - "baiganConfigClasses") + @Test + public void testRegistrationOfBaiganConfigClassesTypes() { + when(metaData.getAnnotationAttributes(ConfigurationServiceScan.class.getName())).thenReturn( + ImmutableMap.of( + "value", new String[]{"org.zalando.baigan.context.packagea"}, + "basePackages", new String[]{"org.zalando.baigan.context.packageb"}, + "basePackageClasses", new Class[]{Marker.class} + ) ); - assertThat(beanDefinition.getAllValues().get(0), instanceOf(GenericBeanDefinition.class)); - assertThat(beanDefinition.getAllValues().get(1).getPropertyValues().get("configTypesByKey"), - equalTo(Map.of("super.sonic.speed", String.class))); + final ArgumentCaptor beanDefinition = ArgumentCaptor.forClass( + AbstractBeanDefinition.class + ); + registrar.registerBeanDefinitions(metaData, registry); + verify(registry, atLeastOnce()).registerBeanDefinition(anyString(), beanDefinition.capture()); + + List definitions = beanDefinition.getAllValues(); + AbstractBeanDefinition classesDefinition = definitions.stream() + .filter(def -> def.getBeanClass().equals(BaiganConfigClasses.class)) + .findFirst().orElseThrow(AssertionError::new); + assertThat( + classesDefinition.getPropertyValues().get("configTypesByKey"), + equalTo( + Map.of( + "super.sonic.speed", String.class, + "ultra.violet.wavelength", Integer.class, + "liquid.crystal.mass", Float.class + ) + ) + ); } + @Test public void whenNothingAnnotatedWithConfigurationServiceScan_shouldThrowException() { - Mockito.when(metaData.getAnnotationAttributes( - ConfigurationServiceScan.class.getName())) - .thenReturn(null); - + when(metaData.getAnnotationAttributes(ConfigurationServiceScan.class.getName())).thenReturn(null); assertThrows(IllegalArgumentException.class, () -> registrar.registerBeanDefinitions(metaData, registry)); - Mockito.when(metaData.getAnnotationAttributes( - ConfigurationServiceScan.class.getName())) - .thenReturn(ImmutableMap.of()); - + when(metaData.getAnnotationAttributes(ConfigurationServiceScan.class.getName())).thenReturn(ImmutableMap.of()); assertThrows(IllegalArgumentException.class, () -> registrar.registerBeanDefinitions(metaData, registry)); - } - -} - -@BaiganConfig -interface SuperSonic { - String speed(); } diff --git a/src/test/java/org/zalando/baigan/context/packagea/SuperSonic.java b/src/test/java/org/zalando/baigan/context/packagea/SuperSonic.java new file mode 100644 index 0000000..3644a9e --- /dev/null +++ b/src/test/java/org/zalando/baigan/context/packagea/SuperSonic.java @@ -0,0 +1,8 @@ +package org.zalando.baigan.context.packagea; + +import org.zalando.baigan.annotation.BaiganConfig; + +@BaiganConfig +interface SuperSonic { + String speed(); +} \ No newline at end of file diff --git a/src/test/java/org/zalando/baigan/context/packageb/UltraViolet.java b/src/test/java/org/zalando/baigan/context/packageb/UltraViolet.java new file mode 100644 index 0000000..7394a9b --- /dev/null +++ b/src/test/java/org/zalando/baigan/context/packageb/UltraViolet.java @@ -0,0 +1,8 @@ +package org.zalando.baigan.context.packageb; + +import org.zalando.baigan.annotation.BaiganConfig; + +@BaiganConfig +interface UltraViolet { + Integer wavelength(); +} \ No newline at end of file diff --git a/src/test/java/org/zalando/baigan/context/packagec/LiquidCrystal.java b/src/test/java/org/zalando/baigan/context/packagec/LiquidCrystal.java new file mode 100644 index 0000000..548ba03 --- /dev/null +++ b/src/test/java/org/zalando/baigan/context/packagec/LiquidCrystal.java @@ -0,0 +1,8 @@ +package org.zalando.baigan.context.packagec; + +import org.zalando.baigan.annotation.BaiganConfig; + +@BaiganConfig +interface LiquidCrystal { + Float mass(); +} \ No newline at end of file diff --git a/src/test/java/org/zalando/baigan/context/packagec/Marker.java b/src/test/java/org/zalando/baigan/context/packagec/Marker.java new file mode 100644 index 0000000..9576f3c --- /dev/null +++ b/src/test/java/org/zalando/baigan/context/packagec/Marker.java @@ -0,0 +1,3 @@ +package org.zalando.baigan.context.packagec; + +public interface Marker { } diff --git a/src/test/java/org/zalando/baigan/e2e/filerepo/FileSystemConfigurationRepositoryEnd2EndIT.java b/src/test/java/org/zalando/baigan/e2e/filerepo/FileSystemConfigurationRepositoryEnd2EndIT.java index c426c52..8cad011 100644 --- a/src/test/java/org/zalando/baigan/e2e/filerepo/FileSystemConfigurationRepositoryEnd2EndIT.java +++ b/src/test/java/org/zalando/baigan/e2e/filerepo/FileSystemConfigurationRepositoryEnd2EndIT.java @@ -101,7 +101,7 @@ public void givenAConfigurationFile_whenConfigurationTypeIsGeneric_thenDeseriali ))); } - @ConfigurationServiceScan(basePackages = "org.zalando.baigan.e2e.configs") + @ConfigurationServiceScan(basePackageClasses = SomeConfiguration.class) @Testcontainers @ComponentScan(basePackageClasses = {BaiganSpringContext.class}) static class RepoConfig {