diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientConfigBean.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientConfigBean.java index 34b177bd1..870e0434b 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientConfigBean.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/EurekaClientConfigBean.java @@ -413,6 +413,13 @@ public class EurekaClientConfigBean implements EurekaClientConfig, Ordered { */ private int order = 0; + /** + * A temporary property to switch between using either RestTemplate or WebClient + * support. + * @return + */ + private boolean webclientSupport = false; + @Override public boolean shouldGZipContent() { return this.gZipContent; @@ -943,6 +950,14 @@ public class EurekaClientConfigBean implements EurekaClientConfig, Ordered { this.order = order; } + public boolean isWebclientSupport() { + return webclientSupport; + } + + public void setWebclientSupport(boolean webclientSupport) { + this.webclientSupport = webclientSupport; + } + @Override public boolean equals(Object o) { if (this == o) { @@ -977,6 +992,7 @@ public class EurekaClientConfigBean implements EurekaClientConfig, Ordered { && allowRedirects == that.allowRedirects && onDemandUpdateStatusChange == that.onDemandUpdateStatusChange && shouldUnregisterOnShutdown == that.shouldUnregisterOnShutdown + && webclientSupport == that.webclientSupport && shouldEnforceRegistrationAtInit == that.shouldEnforceRegistrationAtInit && Objects.equals(proxyPort, that.proxyPort) && Objects.equals(proxyHost, that.proxyHost) @@ -1020,7 +1036,7 @@ public class EurekaClientConfigBean implements EurekaClientConfig, Ordered { filterOnlyUpInstances, fetchRegistry, dollarReplacement, escapeCharReplacement, allowRedirects, onDemandUpdateStatusChange, encoderName, decoderName, clientDataAccept, shouldUnregisterOnShutdown, - shouldEnforceRegistrationAtInit, order); + webclientSupport, shouldEnforceRegistrationAtInit, order); } @Override @@ -1086,7 +1102,8 @@ public class EurekaClientConfigBean implements EurekaClientConfig, Ordered { .append(decoderName).append("', ").append("clientDataAccept='") .append(clientDataAccept).append("', ") .append("shouldUnregisterOnShutdown='").append(shouldUnregisterOnShutdown) - .append("', ").append("shouldEnforceRegistrationAtInit='") + .append("webclientSupport='").append(webclientSupport).append("', ") + .append("shouldEnforceRegistrationAtInit='") .append(shouldEnforceRegistrationAtInit).append("', ").append("order='") .append(order).append("'}").toString(); } diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/config/DiscoveryClientOptionalArgsConfiguration.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/config/DiscoveryClientOptionalArgsConfiguration.java index ccb5096e4..cd5d2cad8 100644 --- a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/config/DiscoveryClientOptionalArgsConfiguration.java +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/config/DiscoveryClientOptionalArgsConfiguration.java @@ -21,9 +21,11 @@ import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.SearchStrategy; import org.springframework.cloud.netflix.eureka.MutableDiscoveryClientOptionalArgs; import org.springframework.cloud.netflix.eureka.http.RestTemplateDiscoveryClientOptionalArgs; +import org.springframework.cloud.netflix.eureka.http.WebClientDiscoveryClientOptionalArgs; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -35,12 +37,26 @@ public class DiscoveryClientOptionalArgsConfiguration { @Bean @ConditionalOnMissingClass("com.sun.jersey.api.client.filter.ClientFilter") - @ConditionalOnMissingBean(value = AbstractDiscoveryClientOptionalArgs.class, + @ConditionalOnMissingBean(value = { AbstractDiscoveryClientOptionalArgs.class }, search = SearchStrategy.CURRENT) + @ConditionalOnProperty(prefix = "eureka.client", name = "webclientSupport", + matchIfMissing = true, havingValue = "false") public RestTemplateDiscoveryClientOptionalArgs restTemplateDiscoveryClientOptionalArgs() { return new RestTemplateDiscoveryClientOptionalArgs(); } + @Bean + @ConditionalOnMissingClass("com.sun.jersey.api.client.filter.ClientFilter") + @ConditionalOnMissingBean( + value = { AbstractDiscoveryClientOptionalArgs.class, + RestTemplateDiscoveryClientOptionalArgs.class }, + search = SearchStrategy.CURRENT) + @ConditionalOnProperty(prefix = "eureka.client", name = "webclientSupport", + havingValue = "true") + public WebClientDiscoveryClientOptionalArgs webClientDiscoveryClientOptionalArgs() { + return new WebClientDiscoveryClientOptionalArgs(); + } + @Bean @ConditionalOnClass(name = "com.sun.jersey.api.client.filter.ClientFilter") @ConditionalOnMissingBean(value = AbstractDiscoveryClientOptionalArgs.class, diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientDiscoveryClientOptionalArgs.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientDiscoveryClientOptionalArgs.java new file mode 100644 index 000000000..a6d45717a --- /dev/null +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientDiscoveryClientOptionalArgs.java @@ -0,0 +1,32 @@ +/* + * Copyright 2017-2019 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 + * + * https://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.netflix.eureka.http; + +import com.netflix.discovery.AbstractDiscoveryClientOptionalArgs; + +/** + * @author Daniel Lavoie + * @author Haytham Mohamed + */ +public class WebClientDiscoveryClientOptionalArgs + extends AbstractDiscoveryClientOptionalArgs { + + public WebClientDiscoveryClientOptionalArgs() { + setTransportClientFactories(new WebClientTransportClientFactories()); + } + +} diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientEurekaHttpClient.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientEurekaHttpClient.java new file mode 100644 index 000000000..a6993158c --- /dev/null +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientEurekaHttpClient.java @@ -0,0 +1,225 @@ +/* + * Copyright 2017-2019 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 + * + * https://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.netflix.eureka.http; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.netflix.appinfo.InstanceInfo; +import com.netflix.appinfo.InstanceInfo.InstanceStatus; +import com.netflix.discovery.shared.Application; +import com.netflix.discovery.shared.Applications; +import com.netflix.discovery.shared.transport.EurekaHttpClient; +import com.netflix.discovery.shared.transport.EurekaHttpResponse; +import com.netflix.discovery.shared.transport.EurekaHttpResponse.EurekaHttpResponseBuilder; +import com.netflix.discovery.util.StringUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.WebClient; + +import static com.netflix.discovery.shared.transport.EurekaHttpResponse.anEurekaHttpResponse; + +/** + * @author Daniel Lavoie + * @author Haytham Mohamed + */ +public class WebClientEurekaHttpClient implements EurekaHttpClient { + + protected final Log logger = LogFactory.getLog(getClass()); + + private WebClient webClient; + + public WebClientEurekaHttpClient(WebClient webClient) { + this.webClient = webClient; + } + + @Override + public EurekaHttpResponse register(InstanceInfo info) { + return webClient.post().uri("apps/" + info.getAppName(), Void.class) + .header(HttpHeaders.ACCEPT_ENCODING, "gzip") + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .exchange().map(response -> eurekaHttpResponse(response)).block(); + } + + @Override + public EurekaHttpResponse cancel(String appName, String id) { + return webClient.delete().uri("apps/" + appName + '/' + id, Void.class).exchange() + .map(response -> eurekaHttpResponse(response)).block(); + } + + @Override + public EurekaHttpResponse sendHeartBeat(String appName, String id, + InstanceInfo info, InstanceStatus overriddenStatus) { + String urlPath = "apps/" + appName + '/' + id + "?status=" + + info.getStatus().toString() + "&lastDirtyTimestamp=" + + info.getLastDirtyTimestamp().toString() + (overriddenStatus != null + ? "&overriddenstatus=" + overriddenStatus.name() : ""); + + ClientResponse response = webClient.put().uri(urlPath, InstanceInfo.class) + .exchange().block(); + + EurekaHttpResponseBuilder builder = anEurekaHttpResponse( + statusCodeValueOf(response), InstanceInfo.class) + .headers(headersOf(response)); + + InstanceInfo entity = response.toEntity(InstanceInfo.class).block().getBody(); + + if (entity != null) { + builder.entity(entity); + } + + return builder.build(); + + } + + @Override + public EurekaHttpResponse statusUpdate(String appName, String id, + InstanceStatus newStatus, InstanceInfo info) { + String urlPath = "apps/" + appName + '/' + id + "/status?value=" + + newStatus.name() + "&lastDirtyTimestamp=" + + info.getLastDirtyTimestamp().toString(); + + return webClient.put().uri(urlPath, Void.class).exchange() + .map(response -> eurekaHttpResponse(response)).block(); + } + + @Override + public EurekaHttpResponse deleteStatusOverride(String appName, String id, + InstanceInfo info) { + String urlPath = "apps/" + appName + '/' + id + "/status?lastDirtyTimestamp=" + + info.getLastDirtyTimestamp().toString(); + + return webClient.delete().uri(urlPath, Void.class).exchange() + .map(response -> eurekaHttpResponse(response)).block(); + } + + @Override + public EurekaHttpResponse getApplications(String... regions) { + return getApplicationsInternal("apps/", regions); + } + + private EurekaHttpResponse getApplicationsInternal(String urlPath, + String[] regions) { + String url = urlPath; + + if (regions != null && regions.length > 0) { + url = url + (urlPath.contains("?") ? "&" : "?") + "regions=" + + StringUtil.join(regions); + } + + ClientResponse response = webClient.get().uri(url, Applications.class).exchange() + .block(); + + int statusCode = statusCodeValueOf(response); + + Applications body = response.toEntity(Applications.class).block().getBody(); + + return anEurekaHttpResponse(statusCode, + statusCode == HttpStatus.OK.value() && body != null ? body : null) + .headers(headersOf(response)).build(); + } + + @Override + public EurekaHttpResponse getDelta(String... regions) { + return getApplicationsInternal("apps/delta", regions); + } + + @Override + public EurekaHttpResponse getVip(String vipAddress, String... regions) { + return getApplicationsInternal("vips/" + vipAddress, regions); + } + + @Override + public EurekaHttpResponse getSecureVip(String secureVipAddress, + String... regions) { + return getApplicationsInternal("svips/" + secureVipAddress, regions); + } + + @Override + public EurekaHttpResponse getApplication(String appName) { + + ClientResponse response = webClient.get() + .uri("apps/" + appName, Application.class).exchange().block(); + + int statusCode = statusCodeValueOf(response); + Application body = response.toEntity(Application.class).block().getBody(); + + Application application = statusCode == HttpStatus.OK.value() && body != null + ? body : null; + + return anEurekaHttpResponse(statusCode, application).headers(headersOf(response)) + .build(); + } + + @Override + public EurekaHttpResponse getInstance(String appName, String id) { + return getInstanceInternal("apps/" + appName + '/' + id); + } + + @Override + public EurekaHttpResponse getInstance(String id) { + return getInstanceInternal("instances/" + id); + } + + private EurekaHttpResponse getInstanceInternal(String urlPath) { + ClientResponse response = webClient.get().uri(urlPath, InstanceInfo.class) + .exchange().block(); + + int statusCode = statusCodeValueOf(response); + InstanceInfo body = response.toEntity(InstanceInfo.class).block().getBody(); + + return anEurekaHttpResponse(statusCode, + statusCode == HttpStatus.OK.value() && body != null ? body : null) + .headers(headersOf(response)).build(); + } + + @Override + public void shutdown() { + // Nothing to do + } + + private static Map headersOf(ClientResponse response) { + ClientResponse.Headers httpHeaders = response.headers(); + if (httpHeaders == null) { + return Collections.emptyMap(); + } + HttpHeaders asHeaders = httpHeaders.asHttpHeaders(); + if (asHeaders == null) { + return Collections.emptyMap(); + } + Map headers = new HashMap<>(); + asHeaders.entrySet().stream().forEach(entry -> entry.getValue().stream() + .forEach(v -> headers.put(entry.getKey(), v))); + return headers; + } + + private int statusCodeValueOf(ClientResponse response) { + return response.statusCode().value(); + } + + private EurekaHttpResponse eurekaHttpResponse(ClientResponse response) { + return anEurekaHttpResponse(statusCodeValueOf(response)) + .headers(headersOf(response)).build(); + } + +} diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientTransportClientFactories.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientTransportClientFactories.java new file mode 100644 index 000000000..584e6293c --- /dev/null +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientTransportClientFactories.java @@ -0,0 +1,59 @@ +/* + * Copyright 2017-2019 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 + * + * https://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.netflix.eureka.http; + +import java.util.Collection; +import java.util.Optional; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; + +import com.netflix.appinfo.InstanceInfo; +import com.netflix.discovery.EurekaClientConfig; +import com.netflix.discovery.shared.transport.TransportClientFactory; +import com.netflix.discovery.shared.transport.jersey.EurekaJerseyClient; +import com.netflix.discovery.shared.transport.jersey.TransportClientFactories; + +/** + * @author Daniel Lavoie + * @author Haytham Mohamed + */ +public class WebClientTransportClientFactories implements TransportClientFactories { + + @Override + public TransportClientFactory newTransportClientFactory( + Collection additionalFilters, EurekaJerseyClient providedJerseyClient) { + throw new UnsupportedOperationException(); + } + + @Override + public TransportClientFactory newTransportClientFactory( + EurekaClientConfig clientConfig, Collection additionalFilters, + InstanceInfo myInstanceInfo) { + return new WebClientTransportClientFactory(); + } + + @Override + public TransportClientFactory newTransportClientFactory( + final EurekaClientConfig clientConfig, + final Collection additionalFilters, final InstanceInfo myInstanceInfo, + final Optional sslContext, + final Optional hostnameVerifier) { + return new WebClientTransportClientFactory(); + } + +} diff --git a/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientTransportClientFactory.java b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientTransportClientFactory.java new file mode 100644 index 000000000..63777cb7f --- /dev/null +++ b/spring-cloud-netflix-eureka-client/src/main/java/org/springframework/cloud/netflix/eureka/http/WebClientTransportClientFactory.java @@ -0,0 +1,196 @@ +/* + * Copyright 2017-2019 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 + * + * https://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.netflix.eureka.http; + +import java.net.URI; +import java.net.URISyntaxException; + +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; +import com.fasterxml.jackson.databind.ser.std.BeanSerializerBase; +import com.netflix.appinfo.InstanceInfo; +import com.netflix.discovery.converters.jackson.mixin.ApplicationsJsonMixIn; +import com.netflix.discovery.converters.jackson.mixin.InstanceInfoJsonMixIn; +import com.netflix.discovery.converters.jackson.serializer.InstanceInfoJsonBeanSerializer; +import com.netflix.discovery.shared.Applications; +import com.netflix.discovery.shared.resolver.EurekaEndpoint; +import com.netflix.discovery.shared.transport.EurekaHttpClient; +import com.netflix.discovery.shared.transport.TransportClientFactory; +import reactor.core.publisher.Mono; + +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.codec.json.Jackson2JsonDecoder; +import org.springframework.http.codec.json.Jackson2JsonEncoder; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.reactive.function.client.ClientResponse; +import org.springframework.web.reactive.function.client.ExchangeFilterFunction; +import org.springframework.web.reactive.function.client.ExchangeFilterFunctions; +import org.springframework.web.reactive.function.client.ExchangeStrategies; +import org.springframework.web.reactive.function.client.WebClient; + +/** + * Provides the custom {@link WebClient.Builder} required by the + * {@link WebClientEurekaHttpClient}. Relies on Jackson for serialization and + * deserialization. + * + * @author Daniel Lavoie + * @author Haytham Mohamed + */ +public class WebClientTransportClientFactory implements TransportClientFactory { + + @Override + public EurekaHttpClient newClient(EurekaEndpoint serviceUrl) { + WebClient.Builder builder = of(serviceUrl.getServiceUrl()); + this.setExchangeStrategies(builder); + this.skipHttp400Error(builder); + return new WebClientEurekaHttpClient(builder.build()); + } + + private WebClient.Builder of(String serviceUrl) { + String url = serviceUrl; + WebClient.Builder builder = WebClient.builder(); + try { + URI serviceURI = new URI(serviceUrl); + if (serviceURI.getUserInfo() != null) { + String[] credentials = serviceURI.getUserInfo().split(":"); + if (credentials.length == 2) { + builder.filter(ExchangeFilterFunctions + .basicAuthentication(credentials[0], credentials[1])); + url = serviceUrl.replace(credentials[0] + ":" + credentials[1] + "@", + ""); + } + } + } + catch (URISyntaxException ignore) { + } + return builder.baseUrl(url); + } + + private void setExchangeStrategies(WebClient.Builder builder) { + ObjectMapper objectMapper = mappingJacksonHttpMessageConverter() + .getObjectMapper(); + ExchangeStrategies strategies = ExchangeStrategies.builder() + .codecs(clientDefaultCodecsConfigurer -> { + clientDefaultCodecsConfigurer.defaultCodecs() + .jackson2JsonEncoder(new Jackson2JsonEncoder(objectMapper, + MediaType.APPLICATION_JSON)); + clientDefaultCodecsConfigurer.defaultCodecs() + .jackson2JsonDecoder(new Jackson2JsonDecoder(objectMapper, + MediaType.APPLICATION_JSON)); + + }).build(); + builder.exchangeStrategies(strategies); + } + + private void skipHttp400Error(WebClient.Builder builder) { + builder.filter(Http4xxErrorExchangeFilterFunction()); + } + + // Skip over 4xx http errors + private ExchangeFilterFunction Http4xxErrorExchangeFilterFunction() { + return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> { + // literally 400 pass the tests, not 4xxClientError + if (clientResponse.statusCode().value() == 400) { + ClientResponse newResponse = ClientResponse.from(clientResponse) + .statusCode(HttpStatus.OK).build(); + newResponse.body((clientHttpResponse, context) -> { + return clientHttpResponse.getBody(); + }); + return Mono.just(newResponse); + } + return Mono.just(clientResponse); + }); + } + + /** + * Provides the serialization configurations required by the Eureka Server. JSON + * content exchanged with eureka requires a root node matching the entity being + * serialized or deserialized. Achieved with + * {@link SerializationFeature#WRAP_ROOT_VALUE} and + * {@link DeserializationFeature#UNWRAP_ROOT_VALUE}. + * {@link PropertyNamingStrategy.SnakeCaseStrategy} is applied to the underlying + * {@link ObjectMapper}. + * @return a {@link MappingJackson2HttpMessageConverter} object + */ + public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() { + MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); + converter.setObjectMapper(new ObjectMapper() + .setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE)); + + SimpleModule jsonModule = new SimpleModule(); + jsonModule.setSerializerModifier(createJsonSerializerModifier()); // keyFormatter, + // compact)); + converter.getObjectMapper().registerModule(jsonModule); + + converter.getObjectMapper().configure(SerializationFeature.WRAP_ROOT_VALUE, true); + converter.getObjectMapper().configure(DeserializationFeature.UNWRAP_ROOT_VALUE, + true); + converter.getObjectMapper().addMixIn(Applications.class, + ApplicationsJsonMixIn.class); + converter.getObjectMapper().addMixIn(InstanceInfo.class, + InstanceInfoJsonMixIn.class); + + // converter.getObjectMapper().addMixIn(DataCenterInfo.class, + // DataCenterInfoXmlMixIn.class); + // converter.getObjectMapper().addMixIn(InstanceInfo.PortWrapper.class, + // PortWrapperXmlMixIn.class); + // converter.getObjectMapper().addMixIn(Application.class, + // ApplicationXmlMixIn.class); + // converter.getObjectMapper().addMixIn(Applications.class, + // ApplicationsXmlMixIn.class); + + return converter; + } + + public static BeanSerializerModifier createJsonSerializerModifier() { // final + // KeyFormatter + // keyFormatter, + // final + // boolean + // compactMode) + // { + return new BeanSerializerModifier() { + @Override + public JsonSerializer modifySerializer(SerializationConfig config, + BeanDescription beanDesc, JsonSerializer serializer) { + /* + * if (beanDesc.getBeanClass().isAssignableFrom(Applications.class)) { + * return new ApplicationsJsonBeanSerializer((BeanSerializerBase) + * serializer, keyFormatter); } + */ + if (beanDesc.getBeanClass().isAssignableFrom(InstanceInfo.class)) { + return new InstanceInfoJsonBeanSerializer( + (BeanSerializerBase) serializer, false); + } + return serializer; + } + }; + } + + @Override + public void shutdown() { + } + +} diff --git a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/config/RestTemplateOptionalArgsConfigurationTest.java b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/config/RestTemplateOptionalArgsConfigurationTest.java index 43577a861..4f342154b 100644 --- a/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/config/RestTemplateOptionalArgsConfigurationTest.java +++ b/spring-cloud-netflix-eureka-client/src/test/java/org/springframework/cloud/netflix/eureka/config/RestTemplateOptionalArgsConfigurationTest.java @@ -24,12 +24,13 @@ import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; import org.springframework.cloud.netflix.eureka.http.RestTemplateDiscoveryClientOptionalArgs; +import org.springframework.cloud.netflix.eureka.http.WebClientDiscoveryClientOptionalArgs; import org.springframework.cloud.netflix.eureka.sample.EurekaSampleApplication; import org.springframework.cloud.test.ClassPathExclusions; import org.springframework.cloud.test.ModifiedClassPathRunner; import org.springframework.context.ConfigurableApplicationContext; -import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; /** * @author Daniel Lavoie @@ -41,12 +42,43 @@ import static org.assertj.core.api.Assertions.assertThat; public class RestTemplateOptionalArgsConfigurationTest { @Test - public void contextLoads() { + public void contextLoadsWithRestTemplate() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder() + .web(WebApplicationType.NONE).sources(EurekaSampleApplication.class) + .properties(new String[] { "eureka.client.webclientSupport=false" }) + .run()) { + assertThat(context.getBean(RestTemplateDiscoveryClientOptionalArgs.class)).isNotNull(); + try { + Object bean = context.getBean(WebClientDiscoveryClientOptionalArgs.class); + assertThat(bean).isNull(); + } catch(Exception ex) {} + } + } + + @Test + public void contextLoadsWithWebClient() { + try (ConfigurableApplicationContext context = new SpringApplicationBuilder() + .web(WebApplicationType.NONE).sources(EurekaSampleApplication.class) + .properties(new String[] { "eureka.client.webclientSupport=true" }) + .run()) { + assertThat(context.getBean(WebClientDiscoveryClientOptionalArgs.class)).isNotNull(); + try { + Object bean = context.getBean(RestTemplateDiscoveryClientOptionalArgs.class); + assertThat(bean).isNull(); + } catch(Exception ex) {} + } + } + + @Test + public void contextLoadsWithRestTemplateAsDefault() { try (ConfigurableApplicationContext context = new SpringApplicationBuilder() .web(WebApplicationType.NONE).sources(EurekaSampleApplication.class) .run()) { - assertThat(context.getBean(RestTemplateDiscoveryClientOptionalArgs.class)) - .isNotNull(); + assertThat(context.getBean(RestTemplateDiscoveryClientOptionalArgs.class)).isNotNull(); + try { + Object bean = context.getBean(WebClientDiscoveryClientOptionalArgs.class); + assertThat(bean).isNull(); + } catch(Exception ex) {} } }