From 75bc2579db47fe9dc4a159a6754f71c0dfc652c2 Mon Sep 17 00:00:00 2001 From: Spencer Gibb Date: Tue, 4 Dec 2018 14:42:32 -0500 Subject: [PATCH] Updates ConfigurationUtils to use ConversionService. Also adds a StringToZonedDateTimeConverter. --- .../config/GatewayAutoConfiguration.java | 17 ++++++-- .../gateway/filter/GatewayMetricsFilter.java | 21 ++++++---- .../predicate/AfterRoutePredicateFactory.java | 9 ++-- .../BeforeRoutePredicateFactory.java | 6 +-- .../BetweenRoutePredicateFactory.java | 20 ++++----- .../route/RouteDefinitionRouteLocator.java | 14 ++++--- .../gateway/route/builder/PredicateSpec.java | 6 +-- .../gateway/support/ConfigurationUtils.java | 9 +++- .../StringToZonedDateTimeConverter.java | 42 +++++++++++++++++++ .../AfterRoutePredicateFactoryTests.java | 17 ++++++-- .../BeforeRoutePredicateFactoryTests.java | 14 ++++++- .../BetweenRoutePredicateFactoryTests.java | 20 ++++++--- .../RouteDefinitionRouteLocatorTests.java | 4 +- .../src/test/resources/application.yml | 1 + 14 files changed, 152 insertions(+), 48 deletions(-) create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/StringToZonedDateTimeConverter.java diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index 21cc0205..faf90563 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -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 GatewayFilters, - List predicates, - RouteDefinitionLocator routeDefinitionLocator) { - return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, GatewayFilters, properties); + List GatewayFilters, + List predicates, + RouteDefinitionLocator routeDefinitionLocator, + ConversionService conversionService) { + return new RouteDefinitionRouteLocator(routeDefinitionLocator, predicates, GatewayFilters, + properties, conversionService); } @Bean diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilter.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilter.java index 11ac4981..afce04dc 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilter.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/GatewayMetricsFilter.java @@ -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)); } } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactory.java index 83a6290a..7174336d 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactory.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactory.java @@ -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 predicates = new LinkedHashMap<>(); private final Map gatewayFilterFactories = new HashMap<>(); private final GatewayProperties gatewayProperties; @@ -69,8 +71,10 @@ public class RouteDefinitionRouteLocator implements RouteLocator, BeanFactoryAwa public RouteDefinitionRouteLocator(RouteDefinitionLocator routeDefinitionLocator, List predicates, List 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 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)); } diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java index 953546af..3274997c 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/PredicateSpec.java @@ -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))); } /** diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ConfigurationUtils.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ConfigurationUtils.java index 41714f8a..59f3c322 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ConfigurationUtils.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/ConfigurationUtils.java @@ -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 properties, String configurationPropertyName, String bindingName, Validator validator) { + bind(o, properties, configurationPropertyName, bindingName, validator, null); + } + + public static void bind(Object o, Map 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) { diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/StringToZonedDateTimeConverter.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/StringToZonedDateTimeConverter.java new file mode 100644 index 00000000..2c5d1229 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/support/StringToZonedDateTimeConverter.java @@ -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 { + @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; + } +} diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactoryTests.java index 1b2f339a..b9248179 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/AfterRoutePredicateFactoryTests.java @@ -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 map = new HashMap<>(); + map.put(DATETIME_KEY, dateString); + AfterRoutePredicateFactory factory = new AfterRoutePredicateFactory(); + + AfterRoutePredicateFactory.Config config = bindConfig(map, factory); + + return factory.apply(config).test(getExchange()); } } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactoryTests.java index 6e7d5e78..a76e0ad9 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BeforeRoutePredicateFactoryTests.java @@ -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 map = new HashMap<>(); + map.put(DATETIME_KEY, dateString); + + BeforeRoutePredicateFactory factory = new BeforeRoutePredicateFactory(); + + BeforeRoutePredicateFactory.Config config = bindConfig(map, factory); + + return factory.apply(config).test(getExchange()); } } diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryTests.java index 97457ea8..a075e157 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/handler/predicate/BetweenRoutePredicateFactoryTests.java @@ -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 bindConfig(HashMap properties, + AbstractRoutePredicateFactory 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); diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java index e619bc55..29283a83 100644 --- a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/route/RouteDefinitionRouteLocatorTests.java @@ -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 routes = routeDefinitionRouteLocator.getRoutes().collectList() .block(); diff --git a/spring-cloud-gateway-core/src/test/resources/application.yml b/spring-cloud-gateway-core/src/test/resources/application.yml index b4e95928..2687cdf9 100644 --- a/spring-cloud-gateway-core/src/test/resources/application.yml +++ b/spring-cloud-gateway-core/src/test/resources/application.yml @@ -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