Updates ConfigurationUtils to use ConversionService.

Also adds a StringToZonedDateTimeConverter.
This commit is contained in:
Spencer Gibb
2018-12-04 14:42:32 -05:00
parent 2cfdf625d9
commit 75bc2579db
14 changed files with 152 additions and 48 deletions

View File

@@ -115,12 +115,14 @@ import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.RouteRefreshListener;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.cloud.gateway.support.StringToZonedDateTimeConverter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.env.Environment;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.util.StringUtils;
@@ -254,6 +256,11 @@ public class GatewayAutoConfiguration {
}
}
@Bean
public StringToZonedDateTimeConverter stringToZonedDateTimeConverter() {
return new StringToZonedDateTimeConverter();
}
//TODO: remove when not needed anymore
// either https://jira.spring.io/browse/SPR-17291 or
// https://github.com/spring-projects/spring-boot/issues/14520 needs to be fixed
@@ -292,10 +299,12 @@ public class GatewayAutoConfiguration {
@Bean
public RouteLocator routeDefinitionRouteLocator(GatewayProperties properties,
List<GatewayFilterFactory> GatewayFilters,
List<RoutePredicateFactory> predicates,
RouteDefinitionLocator routeDefinitionLocator) {
return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, GatewayFilters, properties);
List<GatewayFilterFactory> GatewayFilters,
List<RoutePredicateFactory> predicates,
RouteDefinitionLocator routeDefinitionLocator,
ConversionService conversionService) {
return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, GatewayFilters,
properties, conversionService);
}
@Bean

View File

@@ -17,7 +17,13 @@
package org.springframework.cloud.gateway.filter;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.Timer.Sample;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.publisher.Mono;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.core.Ordered;
@@ -26,15 +32,13 @@ import org.springframework.http.server.reactive.AbstractServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.Timer.Sample;
import reactor.core.publisher.Mono;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;
public class GatewayMetricsFilter implements GlobalFilter, Ordered {
private MeterRegistry meterRegistry;
private final Log log = LogFactory.getLog(getClass());
private final MeterRegistry meterRegistry;
public GatewayMetricsFilter(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
@@ -93,6 +97,9 @@ public class GatewayMetricsFilter implements GlobalFilter, Ordered {
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
Tags tags = Tags.of("outcome", outcome, "status", status, "routeId",
route.getId(), "routeUri", route.getUri().toString());
if (log.isTraceEnabled()) {
log.trace("Stopping timer 'gateway.requests' with tags " + tags);
}
sample.stop(meterRegistry.timer("gateway.requests", tags));
}
}

View File

@@ -24,6 +24,8 @@ import java.util.function.Predicate;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotNull;
import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactory.getZonedDateTime;
/**
@@ -52,13 +54,14 @@ public class AfterRoutePredicateFactory extends AbstractRoutePredicateFactory<Af
}
public static class Config {
private String datetime;
@NotNull
private ZonedDateTime datetime;
public String getDatetime() {
public ZonedDateTime getDatetime() {
return datetime;
}
public void setDatetime(String datetime) {
public void setDatetime(ZonedDateTime datetime) {
this.datetime = datetime;
}
}

View File

@@ -52,13 +52,13 @@ public class BeforeRoutePredicateFactory extends AbstractRoutePredicateFactory<B
}
public static class Config {
private String datetime;
private ZonedDateTime datetime;
public String getDatetime() {
public ZonedDateTime getDatetime() {
return datetime;
}
public void setDatetime(String datetime) {
public void setDatetime(ZonedDateTime datetime) {
this.datetime = datetime;
}
}

View File

@@ -24,12 +24,12 @@ import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
import javax.validation.constraints.NotNull;
import org.springframework.util.Assert;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotEmpty;
/**
* @author Spencer Gibb
*/
@@ -64,25 +64,25 @@ public class BetweenRoutePredicateFactory extends AbstractRoutePredicateFactory<
@Validated
public static class Config {
@NotEmpty
private String datetime1;
@NotEmpty
private String datetime2;
@NotNull
private ZonedDateTime datetime1;
@NotNull
private ZonedDateTime datetime2;
public String getDatetime1() {
public ZonedDateTime getDatetime1() {
return datetime1;
}
public Config setDatetime1(String datetime1) {
public Config setDatetime1(ZonedDateTime datetime1) {
this.datetime1 = datetime1;
return this;
}
public String getDatetime2() {
public ZonedDateTime getDatetime2() {
return datetime2;
}
public Config setDatetime2(String datetime2) {
public Config setDatetime2(ZonedDateTime datetime2) {
this.datetime2 = datetime2;
return this;
}

View File

@@ -45,6 +45,7 @@ import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.validation.Validator;
import org.springframework.web.server.ServerWebExchange;
@@ -59,6 +60,7 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa
protected final Log logger = LogFactory.getLog(getClass());
private final RouteDefinitionLocator routeDefinitionLocator;
private final ConversionService conversionService;
private final Map<String, RoutePredicateFactory> predicates = new LinkedHashMap<>();
private final Map<String, GatewayFilterFactory> gatewayFilterFactories = new HashMap<>();
private final GatewayProperties gatewayProperties;
@@ -69,8 +71,10 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa
public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator,
List<RoutePredicateFactory> predicates,
List<GatewayFilterFactory> gatewayFilterFactories,
GatewayProperties gatewayProperties) {
GatewayProperties gatewayProperties,
ConversionService conversionService) {
this.routeDefinitionLocator = routeDefinitionLocator;
this.conversionService = conversionService;
initFactories(predicates);
gatewayFilterFactories.forEach(factory -> this.gatewayFilterFactories.put(factory.name(), factory));
this.gatewayProperties = gatewayProperties;
@@ -150,8 +154,8 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa
Object configuration = factory.newConfig();
ConfigurationUtils.bind(configuration, properties,
factory.shortcutFieldPrefix(), definition.getName(), validator);
ConfigurationUtils.bind(configuration, properties, factory.shortcutFieldPrefix(),
definition.getName(), validator, conversionService);
GatewayFilter gatewayFilter = factory.apply(configuration);
if (this.publisher != null) {
@@ -218,8 +222,8 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa
Map<String, Object> properties = factory.shortcutType().normalize(args, factory, this.parser, this.beanFactory);
Object config = factory.newConfig();
ConfigurationUtils.bind(config, properties,
factory.shortcutFieldPrefix(), predicate.getName(), validator);
ConfigurationUtils.bind(config, properties, factory.shortcutFieldPrefix(), predicate.getName(),
validator, conversionService);
if (this.publisher != null) {
this.publisher.publishEvent(new PredicateArgsEvent(this, route.getId(), properties));
}

View File

@@ -75,7 +75,7 @@ public class PredicateSpec extends UriSpec {
*/
public BooleanSpec after(ZonedDateTime datetime) {
return asyncPredicate(getBean(AfterRoutePredicateFactory.class)
.applyAsync(c-> c.setDatetime(datetime.toString())));
.applyAsync(c-> c.setDatetime(datetime)));
}
/**
@@ -84,7 +84,7 @@ public class PredicateSpec extends UriSpec {
* @return a {@link BooleanSpec} to be used to add logical operators
*/
public BooleanSpec before(ZonedDateTime datetime) {
return asyncPredicate(getBean(BeforeRoutePredicateFactory.class).applyAsync(c -> c.setDatetime(datetime.toString())));
return asyncPredicate(getBean(BeforeRoutePredicateFactory.class).applyAsync(c -> c.setDatetime(datetime)));
}
/**
@@ -95,7 +95,7 @@ public class PredicateSpec extends UriSpec {
*/
public BooleanSpec between(ZonedDateTime datetime1, ZonedDateTime datetime2) {
return asyncPredicate(getBean(BetweenRoutePredicateFactory.class)
.applyAsync(c -> c.setDatetime1(datetime1.toString()).setDatetime2(datetime2.toString())));
.applyAsync(c -> c.setDatetime1(datetime1).setDatetime2(datetime2)));
}
/**

View File

@@ -17,6 +17,7 @@
package org.springframework.cloud.gateway.support;
import java.util.Collections;
import java.util.Map;
import org.springframework.aop.framework.Advised;
@@ -24,6 +25,7 @@ import org.springframework.aop.support.AopUtils;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.core.convert.ConversionService;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
@@ -32,9 +34,14 @@ import org.springframework.validation.Validator;
public abstract class ConfigurationUtils {
public static void bind(Object o, Map<String, Object> properties, String configurationPropertyName, String bindingName, Validator validator) {
bind(o, properties, configurationPropertyName, bindingName, validator, null);
}
public static void bind(Object o, Map<String, Object> properties, String configurationPropertyName, String bindingName,
Validator validator, ConversionService conversionService) {
Object toBind = getTargetObject(o);
new Binder(new MapConfigurationPropertySource(properties))
new Binder(Collections.singletonList(new MapConfigurationPropertySource(properties)), null, conversionService)
.bind(configurationPropertyName, Bindable.ofInstance(toBind));
if (validator != null) {

View File

@@ -0,0 +1,42 @@
/*
* Copyright 2013-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.springframework.cloud.gateway.support;
import org.springframework.core.convert.converter.Converter;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
public class StringToZonedDateTimeConverter implements Converter<String, ZonedDateTime> {
@Override
public ZonedDateTime convert(String source) {
ZonedDateTime dateTime;
try {
long epoch = Long.parseLong(source);
dateTime = Instant.ofEpochMilli(epoch).atOffset(ZoneOffset.ofTotalSeconds(0))
.toZonedDateTime();
} catch (NumberFormatException e) {
// try ZonedDateTime instead
dateTime = ZonedDateTime.parse(source);
}
return dateTime;
}
}

View File

@@ -17,11 +17,14 @@
package org.springframework.cloud.gateway.handler.predicate;
import java.time.ZonedDateTime;
import java.util.HashMap;
import org.junit.Test;
import java.time.ZonedDateTime;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.gateway.handler.predicate.AfterRoutePredicateFactory.DATETIME_KEY;
import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.bindConfig;
import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.getExchange;
import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.minusHours;
import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.minusHoursMillis;
@@ -71,11 +74,17 @@ public class AfterRoutePredicateFactoryTests {
@Test
public void testPredicates() {
boolean result = new AfterRoutePredicateFactory().apply(c -> c.setDatetime(ZonedDateTime.now().minusHours(2).toString())).test(getExchange());
boolean result = new AfterRoutePredicateFactory().apply(c -> c.setDatetime(ZonedDateTime.now().minusHours(2))).test(getExchange());
assertThat(result).isTrue();
}
private boolean runPredicate(String dateString) {
return new AfterRoutePredicateFactory().apply(c -> c.setDatetime(dateString)).test(getExchange());
HashMap<String, Object> map = new HashMap<>();
map.put(DATETIME_KEY, dateString);
AfterRoutePredicateFactory factory = new AfterRoutePredicateFactory();
AfterRoutePredicateFactory.Config config = bindConfig(map, factory);
return factory.apply(config).test(getExchange());
}
}

View File

@@ -20,8 +20,11 @@ package org.springframework.cloud.gateway.handler.predicate;
import org.junit.Test;
import java.time.ZonedDateTime;
import java.util.HashMap;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.cloud.gateway.handler.predicate.BeforeRoutePredicateFactory.DATETIME_KEY;
import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.bindConfig;
import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.getExchange;
import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.minusHours;
import static org.springframework.cloud.gateway.handler.predicate.BetweenRoutePredicateFactoryTests.minusHoursMillis;
@@ -71,11 +74,18 @@ public class BeforeRoutePredicateFactoryTests {
@Test
public void testPredicates() {
boolean result = new BeforeRoutePredicateFactory().apply(c -> c.setDatetime(ZonedDateTime.now().minusHours(2).toString())).test(getExchange());
boolean result = new BeforeRoutePredicateFactory().apply(c -> c.setDatetime(ZonedDateTime.now().minusHours(2))).test(getExchange());
assertThat(result).isFalse();
}
private boolean runPredicate(String dateString) {
return new BeforeRoutePredicateFactory().apply(c -> c.setDatetime(dateString)).test(getExchange());
HashMap<String, Object> map = new HashMap<>();
map.put(DATETIME_KEY, dateString);
BeforeRoutePredicateFactory factory = new BeforeRoutePredicateFactory();
BeforeRoutePredicateFactory.Config config = bindConfig(map, factory);
return factory.apply(config).test(getExchange());
}
}

View File

@@ -22,7 +22,9 @@ import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import org.junit.Test;
import org.springframework.boot.convert.ApplicationConversionService;
import org.springframework.cloud.gateway.support.ConfigurationUtils;
import org.springframework.cloud.gateway.support.StringToZonedDateTimeConverter;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
import org.springframework.mock.web.server.MockServerWebExchange;
import org.springframework.web.server.ServerWebExchange;
@@ -101,8 +103,8 @@ public class BetweenRoutePredicateFactoryTests {
@Test
public void testPredicates() {
boolean result = new BetweenRoutePredicateFactory()
.apply(c -> c.setDatetime1(ZonedDateTime.now().minusHours(2).toString())
.setDatetime2(ZonedDateTime.now().plusHours(1).toString()))
.apply(c -> c.setDatetime1(ZonedDateTime.now().minusHours(2))
.setDatetime2(ZonedDateTime.now().plusHours(1)))
.test(getExchange());
assertThat(result).isTrue();
}
@@ -114,13 +116,21 @@ public class BetweenRoutePredicateFactoryTests {
BetweenRoutePredicateFactory factory = new BetweenRoutePredicateFactory();
BetweenRoutePredicateFactory.Config config = factory.newConfig();
ConfigurationUtils.bind(config, map, "", "myname", null);
BetweenRoutePredicateFactory.Config config = bindConfig(map, factory);
return factory.apply(config).test(getExchange());
}
static <T> T bindConfig(HashMap<String, Object> properties,
AbstractRoutePredicateFactory<T> factory) {
T config = factory.newConfig();
ApplicationConversionService conversionService = new ApplicationConversionService();
conversionService.addConverter(new StringToZonedDateTimeConverter());
ConfigurationUtils.bind(config, properties, "", "myname", null, conversionService);
return config;
}
static String minusHoursMillis(int hours) {
final int millis = hours * 1000 * 60 * 60;
return String.valueOf(System.currentTimeMillis() - millis);

View File

@@ -5,6 +5,7 @@ import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.config.PropertiesRouteDefinitionLocator;
import org.springframework.cloud.gateway.filter.FilterDefinition;
@@ -17,6 +18,7 @@ import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGate
import org.springframework.cloud.gateway.handler.predicate.HostRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import static org.assertj.core.api.Assertions.assertThat;
@@ -49,7 +51,7 @@ public class RouteDefinitionRouteLocatorTests {
RouteDefinitionRouteLocator routeDefinitionRouteLocator = new RouteDefinitionRouteLocator(
new PropertiesRouteDefinitionLocator(gatewayProperties), predicates,
gatewayFilterFactories, gatewayProperties);
gatewayFilterFactories, gatewayProperties, new DefaultConversionService());
List<Route> routes = routeDefinitionRouteLocator.getRoutes().collectList()
.block();

View File

@@ -334,6 +334,7 @@ logging:
org.springframework.cloud.gateway: TRACE
org.springframework.http.server.reactive: DEBUG
org.springframework.web.reactive: DEBUG
org.springframework.boot.autoconfigure.web: DEBUG
reactor.netty: DEBUG
redisratelimiter: DEBUG