Skip to content

Commit

Permalink
Merge pull request #89 from zalando-stups/package-scan
Browse files Browse the repository at this point in the history
Type-safe package scan for ConfigurationServiceScan
  • Loading branch information
lukasniemeier-zalando authored Dec 5, 2023
2 parents 523cae7 + ead100d commit 0d5b9d2
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {};

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -69,12 +70,17 @@ public void registerBeanDefinitions(

final List<String> 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<String> saneSet = basePackages.stream()
.filter(str -> !StringUtils.isEmpty(str))
.filter(StringUtils::hasText)
.collect(Collectors.toSet());

createAndRegisterBeanDefinitions(saneSet, registry);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<AbstractBeanDefinition> beanDefinition = ArgumentCaptor
.forClass(AbstractBeanDefinition.class);
final ArgumentCaptor<String> 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<String> 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<AbstractBeanDefinition> beanDefinition = ArgumentCaptor.forClass(
AbstractBeanDefinition.class
);
registrar.registerBeanDefinitions(metaData, registry);

verify(registry, atLeastOnce()).registerBeanDefinition(anyString(), beanDefinition.capture());

List<AbstractBeanDefinition> 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();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.zalando.baigan.context.packagea;

import org.zalando.baigan.annotation.BaiganConfig;

@BaiganConfig
interface SuperSonic {
String speed();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.zalando.baigan.context.packageb;

import org.zalando.baigan.annotation.BaiganConfig;

@BaiganConfig
interface UltraViolet {
Integer wavelength();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.zalando.baigan.context.packagec;

import org.zalando.baigan.annotation.BaiganConfig;

@BaiganConfig
interface LiquidCrystal {
Float mass();
}
3 changes: 3 additions & 0 deletions src/test/java/org/zalando/baigan/context/packagec/Marker.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.zalando.baigan.context.packagec;

public interface Marker { }
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 0d5b9d2

Please sign in to comment.