From 586e3710ecf23d37280613cf9c4ac5ce32ac18f4 Mon Sep 17 00:00:00 2001 From: Marcin Grzejszczak Date: Tue, 5 Jun 2018 16:31:49 +0200 Subject: [PATCH] Removed RibbonCommand wrapping without this change the instrumentation of RibbonCommand lead to creation of an additional client span with removal of the instrumentation of the RibbonCommand no longer do we create that span fixes gh-997 --- .../TraceWebClientAutoConfiguration.java | 3 + .../TraceWebClientBeanPostProcessor.java | 2 +- .../web/client/feign/TracingFeignClient.java | 11 ++ .../instrument/zuul/TracePostZuulFilter.java | 7 +- .../zuul/TraceRibbonCommandFactory.java | 171 ------------------ ...RibbonCommandFactoryBeanPostProcessor.java | 55 ------ .../zuul/TraceZuulAutoConfiguration.java | 9 - ...nCommandFactoryBeanPostProcessorTests.java | 56 ------ .../zuul/TraceRibbonCommandFactoryTest.java | 109 ----------- 9 files changed, 21 insertions(+), 402 deletions(-) delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java delete mode 100644 spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java delete mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java delete mode 100644 spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java index 4fa2b851f..73edba394 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientAutoConfiguration.java @@ -383,6 +383,9 @@ class TracingHttpClientInstrumentation { io.netty.handler.codec.http.HttpHeaders tracedHeaders = req .requestHeaders(); span.set(this.handler.handleSend(this.injector, tracedHeaders, req)); + if (log.isDebugEnabled()) { + log.debug("Handled send of " + span.get()); + } io.netty.handler.codec.http.HttpHeaders addedHeaders = tracedHeaders.copy(); originalHeaders.forEach(header -> addedHeaders.remove(header.getKey())); try (Tracer.SpanInScope clientInScope = this.tracer.withSpanInScope(span.get())) { diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java index 6a696421a..ec231112f 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/TraceWebClientBeanPostProcessor.java @@ -179,7 +179,7 @@ class TraceExchangeFilterFunction implements ExchangeFilterFunction { Span clientSpan = handler().handleSend(injector(), builder, request, tracer().nextSpan()); if (log.isDebugEnabled()) { - log.debug("Created a client span for the WebClient " + clientSpan); + log.debug("Handled send of " + clientSpan); } if (parent == null) { c = c.put(Span.class, clientSpan); diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClient.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClient.java index 049103f46..fffcf4749 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClient.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/web/client/feign/TracingFeignClient.java @@ -34,6 +34,8 @@ import brave.propagation.TraceContext; import feign.Client; import feign.Request; import feign.Response; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** * Feign client wrapper @@ -42,6 +44,9 @@ import feign.Response; * @since 2.0.0 */ final class TracingFeignClient implements Client { + + private static final Log log = LogFactory.getLog(TracingFeignClient.class); + static final Propagation.Setter>, String> SETTER = new Propagation.Setter>, String>() { @Override public void put(Map> carrier, String key, @@ -82,6 +87,9 @@ final class TracingFeignClient implements Client { throws IOException { Map> headers = new HashMap<>(request.headers()); Span span = this.handler.handleSend(this.injector, headers, request); + if (log.isDebugEnabled()) { + log.debug("Handled send of " + span); + } Response response = null; Throwable error = null; try (Tracer.SpanInScope ws = this.tracer.withSpanInScope(span)) { @@ -93,6 +101,9 @@ final class TracingFeignClient implements Client { } finally { this.handler.handleReceive(response, error, span); + if (log.isDebugEnabled()) { + log.debug("Handled receive of " + span); + } } } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java index adb5f6d57..f42c0de5c 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TracePostZuulFilter.java @@ -18,6 +18,7 @@ package org.springframework.cloud.sleuth.instrument.zuul; import javax.servlet.http.HttpServletResponse; +import brave.Span; import brave.Tracer; import brave.http.HttpServerHandler; import brave.http.HttpTracing; @@ -67,7 +68,11 @@ class TracePostZuulFilter extends ZuulFilter { } HttpServletResponse response = RequestContext.getCurrentContext().getResponse(); Throwable exception = RequestContext.getCurrentContext().getThrowable(); - this.handler.handleSend(response, exception, this.tracer.currentSpan()); + Span currentSpan = this.tracer.currentSpan(); + this.handler.handleSend(response, exception, currentSpan); + if (log.isDebugEnabled()) { + log.debug("Handled send of " + currentSpan); + } return null; } diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java deleted file mode 100644 index 88892107b..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactory.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * 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.sleuth.instrument.zuul; - -import java.io.IOException; -import java.util.Collections; -import java.util.concurrent.Future; - -import brave.Span; -import brave.Tracer; -import brave.http.HttpClientHandler; -import brave.http.HttpTracing; -import brave.propagation.Propagation; -import brave.propagation.TraceContext; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.SmartInitializingSingleton; -import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext; -import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand; -import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; -import org.springframework.http.client.ClientHttpResponse; -import rx.Observable; - -/** - * Propagates traces downstream via http headers that contain trace metadata. - * - * @author Spencer Gibb - * @author Marcin Grzejszczak - * @since 1.1.0 - */ -class TraceRibbonCommandFactory implements RibbonCommandFactory, - SmartInitializingSingleton { - - static final Propagation.Setter SETTER = new Propagation.Setter() { - @Override public void put(RibbonCommandContext carrier, String key, String value) { - carrier.getHeaders().put(key, Collections.singletonList(value)); - } - - @Override public String toString() { - return "RibbonCommandContext::headers::put"; - } - }; - - private static final Log log = LogFactory.getLog(TraceRibbonCommandFactory.class); - - HttpTracing tracing; - Tracer tracer; - RibbonCommandFactory delegate; - HttpClientHandler handler; - TraceContext.Injector injector; - final BeanFactory beanFactory; - - TraceRibbonCommandFactory(RibbonCommandFactory delegate, BeanFactory beanFactory) { - this.delegate = delegate; - this.beanFactory = beanFactory; - } - - private void initialize() { - if (this.tracing == null) { - this.tracing = httpTracing(); - } - if (this.tracer == null) { - this.tracer = httpTracing().tracing().tracer(); - } - if (this.handler == null) { - this.handler = HttpClientHandler - .create(httpTracing(), new TraceRibbonCommandFactory.HttpAdapter()); - } - if (this.injector == null) { - this.injector = httpTracing().tracing().propagation().injector(SETTER); - } - } - - private HttpTracing httpTracing() { - if (this.tracing == null) { - this.tracing = this.beanFactory.getBean(HttpTracing.class); - } - return this.tracing; - } - - @Override - public RibbonCommand create(final RibbonCommandContext context) { - // just in case - everything should be already initialized - initialize(); - final RibbonCommand ribbonCommand = this.delegate.create(context); - Span span = this.tracer.currentSpan(); - if (log.isDebugEnabled()) { - log.debug("Will set contents of the span " + this.tracer.currentSpan() + " in the ribbon command"); - } - return new RibbonCommand() { - @Override public ClientHttpResponse execute() { - Span span = TraceRibbonCommandFactory.this.handler.handleSend(TraceRibbonCommandFactory.this.injector, context); - ClientHttpResponse response = null; - Throwable error = null; - try (Tracer.SpanInScope ws = TraceRibbonCommandFactory.this.tracer.withSpanInScope(span)) { - return response = ribbonCommand.execute(); - } catch (RuntimeException | Error e) { - if (log.isDebugEnabled()) { - log.debug("Exception occurred while trying to execute ribbon command", e); - } - error = e; - throw e; - } finally { - TraceRibbonCommandFactory.this.handler.handleReceive(response, error, span); - } - } - - // currently only .execute() is used in Zuul - @Override public Future queue() { - parseRequest(context, span); - return ribbonCommand.queue(); - } - - // currently only .execute() is used in Zuul - @Override public Observable observe() { - parseRequest(context, span); - return ribbonCommand.observe(); - } - }; - - } - - private void parseRequest(RibbonCommandContext context, Span span) { - TraceRibbonCommandFactory.this.tracing.clientParser() - .request(new TraceRibbonCommandFactory.HttpAdapter(), context, span); - } - - @Override public void afterSingletonsInstantiated() { - initialize(); - } - - static final class HttpAdapter - extends brave.http.HttpClientAdapter { - - @Override public String method(RibbonCommandContext request) { - return request.getMethod(); - } - - @Override public String url(RibbonCommandContext request) { - return request.getUri(); - } - - @Override public String requestHeader(RibbonCommandContext request, String name) { - Object result = request.getHeaders().getFirst(name); - return result != null ? result.toString() : null; - } - - @Override public Integer statusCode(ClientHttpResponse response) { - try { - return response.getRawStatusCode(); - } catch (IOException e) { - return null; - } - } - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java deleted file mode 100644 index 721efb6d8..000000000 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessor.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.sleuth.instrument.zuul; - -import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.beans.factory.config.BeanPostProcessor; -import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; - -/** - * Post processor that wraps a {@link RibbonCommandFactory} - * in its trace representation. - * - * @author Marcin Grzejszczak - * - * @since 2.0.0 - */ -final class TraceRibbonCommandFactoryBeanPostProcessor implements BeanPostProcessor { - - private final BeanFactory beanFactory; - - TraceRibbonCommandFactoryBeanPostProcessor(BeanFactory beanFactory) { - this.beanFactory = beanFactory; - } - - @Override - public Object postProcessBeforeInitialization(Object bean, String beanName) - throws BeansException { - return bean; - } - - @Override - public Object postProcessAfterInitialization(Object bean, String beanName) - throws BeansException { - if (bean instanceof RibbonCommandFactory - && !(bean instanceof TraceRibbonCommandFactory)) { - return new TraceRibbonCommandFactory((RibbonCommandFactory) bean, this.beanFactory); - } - return bean; - } -} diff --git a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java index 5177e2eaa..65c75d21e 100644 --- a/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java +++ b/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/zuul/TraceZuulAutoConfiguration.java @@ -23,7 +23,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand; import org.springframework.cloud.sleuth.instrument.web.TraceWebServletAutoConfiguration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -48,14 +47,6 @@ public class TraceZuulAutoConfiguration { return new TracePostZuulFilter(httpTracing); } - @ConditionalOnClass(RibbonCommand.class) - static class RibbonConfig { - @Bean - static TraceRibbonCommandFactoryBeanPostProcessor traceRibbonCommandFactoryBeanPostProcessor(BeanFactory beanFactory) { - return new TraceRibbonCommandFactoryBeanPostProcessor(beanFactory); - } - } - @Bean static TraceZuulHandlerMappingBeanPostProcessor traceHandlerMappingBeanPostProcessor(BeanFactory beanFactory) { return new TraceZuulHandlerMappingBeanPostProcessor(beanFactory); diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java deleted file mode 100644 index 8ed0d61d5..000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryBeanPostProcessorTests.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.sleuth.instrument.zuul; - -import brave.Tracing; -import brave.propagation.StrictCurrentTraceContext; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; -import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; - -import static org.assertj.core.api.BDDAssertions.then; - -/** - * @author Marcin Grzejszczak - */ -@RunWith(MockitoJUnitRunner.class) -public class TraceRibbonCommandFactoryBeanPostProcessorTests { - - ArrayListSpanReporter reporter = new ArrayListSpanReporter(); - Tracing tracing = Tracing.newBuilder() - .currentTraceContext(new StrictCurrentTraceContext()) - .spanReporter(this.reporter) - .build(); - - @Mock RibbonCommandFactory ribbonCommandFactory; - @InjectMocks TraceRibbonCommandFactoryBeanPostProcessor postProcessor; - - @Test - public void should_return_a_bean_as_it_is_if_its_not_a_ribbon_command_Factory() { - then(this.postProcessor.postProcessAfterInitialization("", "name")).isEqualTo(""); - } - - @Test - public void should_wrap_ribbon_command_factory_in_a_trace_representation() { - then(this.postProcessor.postProcessAfterInitialization(this.ribbonCommandFactory, "name")).isInstanceOf( - TraceRibbonCommandFactory.class); - } -} \ No newline at end of file diff --git a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java b/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java deleted file mode 100644 index 74f757c02..000000000 --- a/spring-cloud-sleuth-core/src/test/java/org/springframework/cloud/sleuth/instrument/zuul/TraceRibbonCommandFactoryTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * 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.sleuth.instrument.zuul; - -import java.util.ArrayList; - -import brave.ErrorParser; -import brave.Span; -import brave.Tracer; -import brave.Tracing; -import brave.http.HttpTracing; -import brave.propagation.StrictCurrentTraceContext; -import com.netflix.zuul.context.RequestContext; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.BDDMockito; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; -import org.springframework.beans.factory.BeanFactory; -import org.springframework.cloud.netflix.ribbon.support.RibbonCommandContext; -import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommand; -import org.springframework.cloud.netflix.zuul.filters.route.RibbonCommandFactory; -import org.springframework.cloud.sleuth.instrument.web.SleuthHttpParserAccessor; -import org.springframework.cloud.sleuth.util.ArrayListSpanReporter; -import org.springframework.http.HttpHeaders; -import org.springframework.util.LinkedMultiValueMap; - -import static org.assertj.core.api.BDDAssertions.then; - -/** - * @author Marcin Grzejszczak - */ -@RunWith(MockitoJUnitRunner.class) -public class TraceRibbonCommandFactoryTest { - - ArrayListSpanReporter reporter = new ArrayListSpanReporter(); - Tracing tracing = Tracing.newBuilder() - .currentTraceContext(new StrictCurrentTraceContext()) - .spanReporter(this.reporter) - .build(); - HttpTracing httpTracing = HttpTracing.newBuilder(this.tracing) - .clientParser(SleuthHttpParserAccessor.getClient()) - .serverParser(SleuthHttpParserAccessor.getServer(new ErrorParser())) - .build(); - @Mock BeanFactory beanFactory; - @Mock RibbonCommandFactory ribbonCommandFactory; - @Mock RibbonCommand ribbonCommand; - TraceRibbonCommandFactory traceRibbonCommandFactory; - Span span = this.tracing.tracer().nextSpan().name("name"); - - @Before - @SuppressWarnings({ "deprecation", "unchecked" }) - public void setup() { - BDDMockito.given(this.beanFactory.getBean(HttpTracing.class)) - .willReturn(this.httpTracing); - this.traceRibbonCommandFactory = new TraceRibbonCommandFactory( - this.ribbonCommandFactory, this.beanFactory); - BDDMockito.given(this.ribbonCommandFactory - .create(BDDMockito.any(RibbonCommandContext.class))) - .willReturn(this.ribbonCommand); - } - - @After - public void cleanup() { - RequestContext.getCurrentContext().unset(); - this.tracing.close(); - } - - @Test - public void should_attach_trace_headers_to_the_span() throws Exception { - try (Tracer.SpanInScope ws = this.tracing.tracer().withSpanInScope(this.span)) { - RibbonCommand ribbonCommand = this.traceRibbonCommandFactory - .create(ribbonCommandContext()); - ribbonCommand.execute(); - } finally { - this.span.finish(); - } - - then(this.reporter.getSpans()).hasSize(2); - // RPC - zipkin2.Span span = this.reporter.getSpans().get(0); - then(span.tags()) - .containsEntry("http.method", "GET") - .containsEntry("http.url", "http://localhost:1234/foo"); - zipkin2.Span main = this.reporter.getSpans().get(1); - then(main.name()).isEqualTo("name"); - } - - private RibbonCommandContext ribbonCommandContext() { - return new RibbonCommandContext("serviceId", "GET", "http://localhost:1234/foo", - false, new HttpHeaders(), new LinkedMultiValueMap<>(), null, new ArrayList<>()); - } -}