Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate configurations and add configuration tests #162

Merged
merged 11 commits into from
Aug 5, 2019
4 changes: 2 additions & 2 deletions app/src/main/java/com/github/bsideup/liiklus/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ public static SpringApplication createSpringApplication(String[] args) {
environment.setDefaultProfiles("exporter", "gateway");
environment.getPropertySources().addFirst(new SimpleCommandLinePropertySource(args));

var pluginsDir = environment.getProperty("plugins.dir", String.class, "./plugins");
var pathMatcher = environment.getProperty("plugins.pathMatcher", String.class, "*.jar");
var pluginsDir = environment.getRequiredProperty("plugins.dir", String.class);
lanwen marked this conversation as resolved.
Show resolved Hide resolved
var pathMatcher = environment.getRequiredProperty("plugins.pathMatcher", String.class);

var pluginsRoot = Paths.get(pluginsDir).toAbsolutePath().normalize();
log.info("Loading plugins from '{}' with matcher: '{}'", pluginsRoot, pathMatcher);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import com.github.bsideup.liiklus.records.RecordPostProcessor;
import com.github.bsideup.liiklus.records.RecordPreProcessor;
import com.github.bsideup.liiklus.service.LiiklusService;
import com.github.bsideup.liiklus.util.PropertiesUtil;
import lombok.Data;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.Profiles;
Expand All @@ -24,8 +25,7 @@ public void initialize(GenericApplicationContext applicationContext) {
return;
}

var binder = Binder.get(environment);
var layersProperties = binder.bind("layers", LayersProperties.class).orElseGet(LayersProperties::new);
var layersProperties = PropertiesUtil.bind(environment, new LayersProperties());

var comparator = Comparator
.comparingInt(it -> layersProperties.getOrders().getOrDefault(it.getClass().getName(), 0))
Expand All @@ -46,6 +46,7 @@ public void initialize(GenericApplicationContext applicationContext) {
applicationContext.registerBean(LiiklusService.class);
}

@ConfigurationProperties("layers")
lanwen marked this conversation as resolved.
Show resolved Hide resolved
@Data
static class LayersProperties {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.github.bsideup.liiklus.util;

import lombok.NonNull;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.bind.validation.ValidationBindHandler;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.validation.beanvalidation.SpringValidatorAdapter;

import javax.validation.Validation;

public class PropertiesUtil {

public static <T> T bind(ConfigurableEnvironment environment, @NonNull T properties) {
var configurationProperties = AnnotationUtils.findAnnotation(properties.getClass(), ConfigurationProperties.class);

if (configurationProperties == null) {
throw new IllegalArgumentException(properties.getClass() + " Must be annotated with @ConfigurationProperties");
}

var property = configurationProperties.prefix();

var validationBindHandler = new ValidationBindHandler(
new SpringValidatorAdapter(Validation.buildDefaultValidatorFactory().getValidator())
);

var bindable = Bindable.ofInstance(properties);
return Binder.get(environment).bind(property, bindable, validationBindHandler).orElseGet(bindable.getValue());
}
}
4 changes: 4 additions & 0 deletions app/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
grpc:
port: 6565

rsocket:
host: 0.0.0.0
port: 8081

plugins:
dir: ./plugins
pathMatcher: "*.jar"
18 changes: 8 additions & 10 deletions app/src/test/java/com/github/bsideup/liiklus/ProfilesTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,7 @@ public class ProfilesTest extends AbstractIntegrationTest {
"storage.positions.type=MEMORY"
);

Set<String> commonArgs = Sets.newHashSet(
"grpc.inProcessServerName=liiklus-profile-test"
);
Set<String> commonArgs = Sets.newHashSet();

ConfigurableApplicationContext lastApplicationContext;

Expand All @@ -40,13 +38,13 @@ public void tearDown() throws Exception {
@Test
public void testRequired() throws Exception {
assertThatAppWithProps(commonArgs)
.hasRootCauseInstanceOf(NoSuchBeanDefinitionException.class);
.hasMessageContaining("Required key");

assertThatAppWithProps(commonArgs, RECORDS_PROPERTIES)
.hasRootCauseInstanceOf(NoSuchBeanDefinitionException.class);
.hasMessageContaining("Required key");

assertThatAppWithProps(commonArgs, POSITIONS_PROPERTIES)
.hasRootCauseInstanceOf(NoSuchBeanDefinitionException.class);
.hasMessageContaining("Required key");

assertThatAppWithProps(commonArgs, RECORDS_PROPERTIES, POSITIONS_PROPERTIES)
.doesNotThrowAnyException();
Expand All @@ -57,7 +55,7 @@ public void testExporterProfile() throws Exception {
commonArgs.add("spring.profiles.active=exporter");

assertThatAppWithProps(commonArgs)
.hasRootCauseInstanceOf(NoSuchBeanDefinitionException.class);
.hasMessageContaining("Required key");

assertThatAppWithProps(commonArgs, POSITIONS_PROPERTIES)
.doesNotThrowAnyException();
Expand All @@ -68,13 +66,13 @@ public void testGatewayProfile() throws Exception {
commonArgs.add("spring.profiles.active=gateway");

assertThatAppWithProps(commonArgs)
.hasRootCauseInstanceOf(NoSuchBeanDefinitionException.class);
.hasMessageContaining("Required key");

assertThatAppWithProps(commonArgs, RECORDS_PROPERTIES)
.hasRootCauseInstanceOf(NoSuchBeanDefinitionException.class);
.hasMessageContaining("Required key");

assertThatAppWithProps(commonArgs, POSITIONS_PROPERTIES)
.hasRootCauseInstanceOf(NoSuchBeanDefinitionException.class);
.hasMessageContaining("Required key");

assertThatAppWithProps(commonArgs, RECORDS_PROPERTIES, POSITIONS_PROPERTIES)
.doesNotThrowAnyException();
Expand Down
1 change: 1 addition & 0 deletions plugins/dynamodb-positions-storage/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ dependencies {
testAnnotationProcessor 'org.projectlombok:lombok'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
testCompile project(":tck")
testCompile 'org.springframework.boot:spring-boot-test'
testCompile 'org.testcontainers:localstack'
testCompile 'com.amazonaws:aws-java-sdk-dynamodb:1.11.475'
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.github.bsideup.liiklus.dynamodb.config;

import com.github.bsideup.liiklus.dynamodb.DynamoDBPositionsStorage;
import com.github.bsideup.liiklus.positions.PositionsStorage;
import com.github.bsideup.liiklus.util.PropertiesUtil;
import com.google.auto.service.AutoService;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.validation.annotation.Validated;
Expand All @@ -29,15 +29,13 @@ public class DynamoDBConfiguration implements ApplicationContextInitializer<Gene
public void initialize(GenericApplicationContext applicationContext) {
var environment = applicationContext.getEnvironment();

if (!"DYNAMODB".equals(environment.getProperty("storage.positions.type"))) {
if (!"DYNAMODB".equals(environment.getRequiredProperty("storage.positions.type"))) {
return;
}

var binder = Binder.get(environment);
var dynamoDBProperties = binder.bind("dynamodb", DynamoDBProperties.class).orElseGet(DynamoDBProperties::new);
var dynamoDBProperties = PropertiesUtil.bind(environment, new DynamoDBProperties());


applicationContext.registerBean(PositionsStorage.class, () -> {
applicationContext.registerBean(DynamoDBPositionsStorage.class, () -> {
var builder = DynamoDbAsyncClient.builder();

dynamoDBProperties.getEndpoint()
Expand Down Expand Up @@ -76,6 +74,7 @@ public void initialize(GenericApplicationContext applicationContext) {
});
}

@ConfigurationProperties("dynamodb")
@Data
@Validated
public static class DynamoDBProperties {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,17 @@ class DynamoDBPositionsStorageTest implements PositionsStorageTests {

static {
localstack.start();

System.setProperty("dynamodb.autoCreateTable", "true");
System.setProperty("dynamodb.positionsTable", "positions-" + UUID.randomUUID());
var endpointConfiguration = localstack.getEndpointConfiguration(Service.DYNAMODB);
System.setProperty("dynamodb.endpoint", endpointConfiguration.getServiceEndpoint());
System.setProperty("aws.region", endpointConfiguration.getSigningRegion());
var credentials = localstack.getDefaultCredentialsProvider().getCredentials();
System.setProperty("aws.accessKeyId", credentials.getAWSAccessKeyId());
System.setProperty("aws.secretAccessKey", credentials.getAWSSecretKey());

applicationContext = new ApplicationRunner("MEMORY", "DYNAMODB").run();
applicationContext = new ApplicationRunner("MEMORY", "DYNAMODB")
.withProperty("dynamodb.autoCreateTable", "true")
.withProperty("dynamodb.positionsTable", "positions-" + UUID.randomUUID())
.withProperty("dynamodb.endpoint", endpointConfiguration.getServiceEndpoint())
.run();
}

@Getter
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.github.bsideup.liiklus.dynamodb.config;

import com.github.bsideup.liiklus.dynamodb.DynamoDBPositionsStorage;
import com.github.bsideup.liiklus.positions.PositionsStorage;
import org.junit.jupiter.api.Test;
import org.springframework.beans.BeansException;
import org.springframework.boot.context.properties.bind.validation.BindValidationException;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.support.StaticApplicationContext;

import static org.assertj.core.api.Assertions.assertThat;

class DynamoDBConfigurationTest {

ApplicationContextRunner applicationContextRunner = new ApplicationContextRunner(() -> new StaticApplicationContext() {
@Override
public void refresh() throws BeansException, IllegalStateException {
}
})
.withInitializer((ApplicationContextInitializer) new DynamoDBConfiguration());

@Test
void shouldSkipWhenNotDynamoDB() {
applicationContextRunner = applicationContextRunner.withPropertyValues(
"storage.positions.type: FOO"
);
applicationContextRunner.run(context -> {
assertThat(context).doesNotHaveBean(PositionsStorage.class);
});
}

@Test
void shouldValidateProperties() {
applicationContextRunner = applicationContextRunner.withPropertyValues(
"storage.positions.type: DYNAMODB"
);
applicationContextRunner.run(context -> {
assertThat(context)
.getFailure()
.hasCauseInstanceOf(BindValidationException.class);
});
applicationContextRunner = applicationContextRunner.withPropertyValues(
"dynamodb.positionsTable: foo"
);
applicationContextRunner.run(context -> {
assertThat(context).hasNotFailed();
});
}

@Test
void shouldRegisterWhenDynamoDB() {
applicationContextRunner = applicationContextRunner.withPropertyValues(
"storage.positions.type: DYNAMODB",
"dynamodb.positionsTable: foo"
);
applicationContextRunner.run(context -> {
assertThat(context).hasSingleBean(DynamoDBPositionsStorage.class);
});
}

}
10 changes: 10 additions & 0 deletions plugins/grpc-transport/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,14 @@ dependencies {
compile 'io.grpc:grpc-protobuf'
compile 'io.grpc:grpc-netty'
compile 'io.grpc:grpc-services'

testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
testCompile 'org.junit.jupiter:junit-jupiter-api'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
lanwen marked this conversation as resolved.
Show resolved Hide resolved

testCompile project(":app")
testCompile 'org.springframework.boot:spring-boot-test'
testCompile 'org.assertj:assertj-core'
testCompile 'org.mockito:mockito-core'
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
package com.github.bsideup.liiklus.transport.grpc.config;

import com.github.bsideup.liiklus.transport.grpc.GRPCLiiklusService;
import com.github.bsideup.liiklus.util.PropertiesUtil;
import com.google.auto.service.AutoService;
import io.grpc.*;
import io.grpc.netty.NettyServerBuilder;
import io.grpc.protobuf.services.ProtoReflectionService;
import io.netty.channel.nio.NioEventLoopGroup;
import lombok.Data;
import org.springframework.boot.context.properties.bind.Binder;
import org.hibernate.validator.group.GroupSequenceProvider;
import org.hibernate.validator.spi.group.DefaultGroupSequenceProvider;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.Profiles;
import reactor.core.scheduler.Schedulers;

import javax.validation.constraints.Min;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

@AutoService(ApplicationContextInitializer.class)
Expand All @@ -26,9 +32,7 @@ public void initialize(GenericApplicationContext applicationContext) {
return;
}

var binder = Binder.get(environment);

var serverProperties = binder.bind("grpc", GRpcServerProperties.class).orElseGet(GRpcServerProperties::new);
var serverProperties = PropertiesUtil.bind(environment, new GRpcServerProperties());

if (!serverProperties.isEnabled()) {
return;
Expand Down Expand Up @@ -67,13 +71,31 @@ public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(ServerCall<ReqT, Re
);
}

@ConfigurationProperties("grpc")
@Data
@GroupSequenceProvider(GRpcServerProperties.EnabledSequenceProvider.class)
static class GRpcServerProperties {

int port = 6565;

boolean enabled = true;

@Min(value = 0, groups = Enabled.class)
int port = -1;

interface Enabled {}

public static class EnabledSequenceProvider implements DefaultGroupSequenceProvider<GRpcServerProperties> {

@Override
public List<Class<?>> getValidationGroups(GRpcServerProperties object) {
var sequence = new ArrayList<Class<?>>();
sequence.add(GRpcServerProperties.class);
if (object != null && object.isEnabled()) {
sequence.add(Enabled.class);
}
return sequence;
}
}

}

}
Loading