This is an automated email from the ASF dual-hosted git repository. reta pushed a commit to branch 3.6.x-fixes in repository https://gitbox.apache.org/repos/asf/cxf.git
commit a1531cfe652275d85f061fc05ac9a9d62aaa3919 Author: Andriy Redko <[email protected]> AuthorDate: Sun Aug 10 09:04:34 2025 -0400 CXF-9152: Adding @Timed annotation to JaxWS WebService class breaks Micrometer integration (#2539) (cherry picked from commit d66e4b60fbc0fe153a7a1dec2d67e2b21b6478e6) (cherry picked from commit 073a3a0feb96f16a42a01800aeb151a8cbceeac3) --- .../SpringBasedTimedAnnotationProvider.java | 11 + .../micrometer/MicrometerMetricsContext.java | 25 +- .../provider/DefaultTimedAnnotationProvider.java | 11 + .../provider/TimedAnnotationProvider.java | 5 + .../MicrometerClientMetricsContextTest.java | 2 +- .../MicrometerServerMetricsContextTest.java | 70 +++++- .../resources/HelloServiceEmptyTimedImpl.java | 19 +- .../jaxws/resources/HelloServiceTimedImpl.java | 19 +- .../jaxws/spring/boot/SpringJaxwsTimedTest.java | 271 +++++++++++++++++++++ 9 files changed, 413 insertions(+), 20 deletions(-) diff --git a/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/provider/SpringBasedTimedAnnotationProvider.java b/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/provider/SpringBasedTimedAnnotationProvider.java index 3533e23bc1..5c763f8446 100644 --- a/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/provider/SpringBasedTimedAnnotationProvider.java +++ b/integration/spring-boot/autoconfigure/src/main/java/org/apache/cxf/spring/boot/autoconfigure/micrometer/provider/SpringBasedTimedAnnotationProvider.java @@ -22,6 +22,7 @@ package org.apache.cxf.spring.boot.autoconfigure.micrometer.provider; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Method; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -66,6 +67,16 @@ public class SpringBasedTimedAnnotationProvider implements TimedAnnotationProvid }); } + @Override + public Optional<String> getDefaultMetricName(Exchange ex, boolean client) { + final HandlerMethod handlerMethod = HandlerMethod.create(ex, client); + if (handlerMethod == null) { + return Optional.empty(); + } else { + return Optional.of(handlerMethod.method.getName()); + } + } + Set<Timed> findTimedAnnotations(AnnotatedElement element) { return MergedAnnotations.from(element).stream(Timed.class) .collect(MergedAnnotationCollectors.toAnnotationSet()); diff --git a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsContext.java b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsContext.java index 955b016377..aaa10c5913 100644 --- a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsContext.java +++ b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/MicrometerMetricsContext.java @@ -20,6 +20,7 @@ package org.apache.cxf.metrics.micrometer; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.function.Supplier; import java.util.logging.Logger; @@ -99,15 +100,27 @@ abstract class MicrometerMetricsContext implements MetricsContext { Timer.Sample timerSample = timingContext.getTimerSample(); Supplier<Iterable<Tag>> tags = () -> getAllTags(ex); - if (annotations.isEmpty()) { - if (this.autoTimeRequests) { - stop(timerSample, tags, Timer.builder(this.metricName)); - } - } else { + boolean defaultMetricReported = false; + if (!annotations.isEmpty()) { + final Optional<String> defaultMetricNameOpt = timedAnnotationProvider + .getDefaultMetricName(ex, client); + for (Timed annotation : annotations) { - stop(timerSample, tags, Timer.builder(annotation, this.metricName)); + // Edge case, the @Timed without value would be reported as a default one. + // We should not report the same metric more than once. + if (annotation.value().isEmpty() && defaultMetricReported) { + continue; + } + + defaultMetricReported |= defaultMetricNameOpt.isEmpty() && annotation.value().isEmpty(); + stop(timerSample, tags, Timer.builder(annotation, + defaultMetricNameOpt.orElse(this.metricName))); } } + + if (this.autoTimeRequests && !defaultMetricReported) { + stop(timerSample, tags, Timer.builder(this.metricName)); + } } private void startAndAttachTimingContext(Message request) { diff --git a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/DefaultTimedAnnotationProvider.java b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/DefaultTimedAnnotationProvider.java index 03afd292be..2d5b63b8cb 100644 --- a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/DefaultTimedAnnotationProvider.java +++ b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/DefaultTimedAnnotationProvider.java @@ -25,6 +25,7 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.HashSet; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -65,6 +66,16 @@ public class DefaultTimedAnnotationProvider implements TimedAnnotationProvider { return timed; }); } + + @Override + public Optional<String> getDefaultMetricName(Exchange ex, boolean client) { + final HandlerMethod handlerMethod = HandlerMethod.create(ex, client); + if (handlerMethod == null) { + return Optional.empty(); + } else { + return Optional.of(handlerMethod.method.getName()); + } + } Set<Timed> findTimedAnnotations(AnnotatedElement element) { Set<Annotation> foundAnnotations = new HashSet<>(); diff --git a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/TimedAnnotationProvider.java b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/TimedAnnotationProvider.java index d8d0fe2f0f..8aec51085d 100644 --- a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/TimedAnnotationProvider.java +++ b/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/TimedAnnotationProvider.java @@ -19,6 +19,7 @@ package org.apache.cxf.metrics.micrometer.provider; +import java.util.Optional; import java.util.Set; import org.apache.cxf.message.Exchange; @@ -28,4 +29,8 @@ import io.micrometer.core.annotation.Timed; public interface TimedAnnotationProvider { Set<Timed> getTimedAnnotations(Exchange ex, boolean client); + + default Optional<String> getDefaultMetricName(Exchange ex, boolean client) { + return Optional.empty(); + } } diff --git a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/MicrometerClientMetricsContextTest.java b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/MicrometerClientMetricsContextTest.java index b53097e211..e6c522317f 100644 --- a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/MicrometerClientMetricsContextTest.java +++ b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/MicrometerClientMetricsContextTest.java @@ -183,7 +183,7 @@ public class MicrometerClientMetricsContextTest { underTest.stop(DUMMY_LONG, DUMMY_LONG, DUMMY_LONG, exchange); // then - verify(sample, times(2)).stop(timerArgumentCaptor.capture()); + verify(sample, times(3)).stop(timerArgumentCaptor.capture()); List<Meter.Id> timers = timerArgumentCaptor.getAllValues().stream().map(Meter::getId) .collect(Collectors.toList()); diff --git a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/MicrometerServerMetricsContextTest.java b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/MicrometerServerMetricsContextTest.java index 11fb9754d3..df1e99006e 100644 --- a/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/MicrometerServerMetricsContextTest.java +++ b/rt/features/metrics/src/test/java/org/apache/cxf/metrics/micrometer/MicrometerServerMetricsContextTest.java @@ -185,7 +185,7 @@ public class MicrometerServerMetricsContextTest { underTest.stop(DUMMY_LONG, DUMMY_LONG, DUMMY_LONG, exchange); // then - verify(sample, times(2)).stop(timerArgumentCaptor.capture()); + verify(sample, times(3)).stop(timerArgumentCaptor.capture()); List<Meter.Id> timers = timerArgumentCaptor.getAllValues().stream().map(Meter::getId) .collect(Collectors.toList()); @@ -203,6 +203,74 @@ public class MicrometerServerMetricsContextTest { Meter.Type.OTHER))); } + @Test + public void testStopShoulCallStopOnTimedAnnotationAndDoNotEmitDefaultMetric() { + // given + doReturn(new HashSet<>(asList(firstTimedAnnotation))) + .when(timedAnnotationProvider).getTimedAnnotations(exchange, false); + + doReturn("").when(firstTimedAnnotation).value(); + doReturn("").when(firstTimedAnnotation).description(); + doReturn(new double[]{}).when(firstTimedAnnotation).percentiles(); + + + TimingContext timingContext = new TimingContext(sample); + + doReturn(timingContext).when(request).getContent(TimingContext.class); + + // when + underTest.stop(DUMMY_LONG, DUMMY_LONG, DUMMY_LONG, exchange); + + // then + verify(sample, times(1)).stop(timerArgumentCaptor.capture()); + + List<Meter.Id> timers = timerArgumentCaptor.getAllValues().stream().map(Meter::getId) + .collect(Collectors.toList()); + + assertThat(timers, hasItems( + new Meter.Id(DUMMY_METRIC, + Tags.of(DEFAULT_DUMMY_TAG, FIRST_ADDITIONAL_DUMMY_TAG, SECOND_ADDITIONAL_DUMMY_TAG), + null, + null, + Meter.Type.OTHER))); + } + + @Test + public void testStopShoulCallStopOnAllTimedAnnotationsAndDoNotEmitDefaultMetric() { + // given + doReturn(new HashSet<>(asList(firstTimedAnnotation, secondTimedAnnotation))) + .when(timedAnnotationProvider).getTimedAnnotations(exchange, false); + + doReturn("").when(firstTimedAnnotation).value(); + doReturn("").when(firstTimedAnnotation).description(); + doReturn(new double[]{}).when(firstTimedAnnotation).percentiles(); + + doReturn("").when(secondTimedAnnotation).value(); + doReturn("").when(secondTimedAnnotation).description(); + doReturn(new double[]{}).when(secondTimedAnnotation).percentiles(); + + + TimingContext timingContext = new TimingContext(sample); + + doReturn(timingContext).when(request).getContent(TimingContext.class); + + // when + underTest.stop(DUMMY_LONG, DUMMY_LONG, DUMMY_LONG, exchange); + + // then + verify(sample, times(1)).stop(timerArgumentCaptor.capture()); + + List<Meter.Id> timers = timerArgumentCaptor.getAllValues().stream().map(Meter::getId) + .collect(Collectors.toList()); + + assertThat(timers, hasItems( + new Meter.Id(DUMMY_METRIC, + Tags.of(DEFAULT_DUMMY_TAG, FIRST_ADDITIONAL_DUMMY_TAG, SECOND_ADDITIONAL_DUMMY_TAG), + null, + null, + Meter.Type.OTHER))); + } + private Object getTimer(ArgumentCaptor<TimingContext> content) throws NoSuchFieldException { Timer.Sample timerSample = content.getValue().getTimerSample(); diff --git a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/TimedAnnotationProvider.java b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxws/resources/HelloServiceEmptyTimedImpl.java similarity index 62% copy from rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/TimedAnnotationProvider.java copy to systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxws/resources/HelloServiceEmptyTimedImpl.java index d8d0fe2f0f..0b0c05f490 100644 --- a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/TimedAnnotationProvider.java +++ b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxws/resources/HelloServiceEmptyTimedImpl.java @@ -16,16 +16,23 @@ * specific language governing permissions and limitations * under the License. */ +package org.apache.cxf.systest.jaxws.resources; -package org.apache.cxf.metrics.micrometer.provider; +import javax.jws.WebService; -import java.util.Set; +import io.micrometer.core.annotation.Timed; -import org.apache.cxf.message.Exchange; +import static java.util.Objects.requireNonNull; -import io.micrometer.core.annotation.Timed; +@WebService(serviceName = "HelloService", portName = "HelloPort", + targetNamespace = "http://service.ws.sample/", + endpointInterface = "org.apache.cxf.systest.jaxws.resources.HelloService") +public class HelloServiceEmptyTimedImpl implements HelloService { -public interface TimedAnnotationProvider { + @Timed + public String sayHello(String name) { + requireNonNull(name); + return "Hello, " + name; + } - Set<Timed> getTimedAnnotations(Exchange ex, boolean client); } diff --git a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/TimedAnnotationProvider.java b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxws/resources/HelloServiceTimedImpl.java similarity index 61% copy from rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/TimedAnnotationProvider.java copy to systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxws/resources/HelloServiceTimedImpl.java index d8d0fe2f0f..866d93ce47 100644 --- a/rt/features/metrics/src/main/java/org/apache/cxf/metrics/micrometer/provider/TimedAnnotationProvider.java +++ b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxws/resources/HelloServiceTimedImpl.java @@ -16,16 +16,23 @@ * specific language governing permissions and limitations * under the License. */ +package org.apache.cxf.systest.jaxws.resources; -package org.apache.cxf.metrics.micrometer.provider; +import javax.jws.WebService; -import java.util.Set; +import io.micrometer.core.annotation.Timed; -import org.apache.cxf.message.Exchange; +import static java.util.Objects.requireNonNull; -import io.micrometer.core.annotation.Timed; +@WebService(serviceName = "HelloService", portName = "HelloPort", + targetNamespace = "http://service.ws.sample/", + endpointInterface = "org.apache.cxf.systest.jaxws.resources.HelloService") +public class HelloServiceTimedImpl implements HelloService { -public interface TimedAnnotationProvider { + @Timed(value = "sayHello.requests") + public String sayHello(String name) { + requireNonNull(name); + return "Hello, " + name; + } - Set<Timed> getTimedAnnotations(Exchange ex, boolean client); } diff --git a/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxws/spring/boot/SpringJaxwsTimedTest.java b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxws/spring/boot/SpringJaxwsTimedTest.java new file mode 100644 index 0000000000..f75e6ed4db --- /dev/null +++ b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxws/spring/boot/SpringJaxwsTimedTest.java @@ -0,0 +1,271 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.apache.cxf.systest.jaxws.spring.boot; + +import java.io.StringReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.time.Duration; +import java.util.Map; + +import javax.xml.namespace.QName; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import javax.xml.ws.Dispatch; +import javax.xml.ws.Endpoint; +import javax.xml.ws.Service; +import javax.xml.ws.Service.Mode; + +import org.apache.cxf.Bus; +import org.apache.cxf.jaxws.EndpointImpl; +import org.apache.cxf.metrics.MetricsFeature; +import org.apache.cxf.metrics.MetricsProvider; +import org.apache.cxf.staxutils.StaxUtils; +import org.apache.cxf.systest.jaxws.resources.HelloServiceEmptyTimedImpl; +import org.apache.cxf.systest.jaxws.resources.HelloServiceTimedImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.ImportResource; +import org.springframework.test.context.ActiveProfiles; + +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.search.MeterNotFoundException; +import io.micrometer.core.instrument.search.RequiredSearch; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static java.util.stream.Collectors.toMap; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; +import static org.awaitility.Awaitility.await; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.Matchers.empty; + +@SpringBootApplication +@SpringBootTest( + webEnvironment = WebEnvironment.RANDOM_PORT, + classes = { + SpringJaxwsTimedTest.TestConfig.class, + + }, + properties = { + "cxf.metrics.server.max-uri-tags=2" + }) +@ImportResource("classpath:spring/jaxws-client.xml") +@ActiveProfiles("jaxws") + +public class SpringJaxwsTimedTest { + + private static final String DUMMY_REQUEST_BODY = "<q0:sayHello xmlns:q0=\"http://service.ws.sample/\">" + + "<name>Elan</name>" + + "</q0:sayHello>"; + private static final String HELLO_SERVICE_NAME_V1 = "HelloV1"; + private static final String HELLO_SERVICE_NAME_V2 = "HelloV2"; + + @Autowired + private MeterRegistry registry; + + @Autowired + private MetricsProvider metricsProvider; + + @LocalServerPort + private int port; + + @EnableAutoConfiguration + static class TestConfig { + @Autowired + private Bus bus; + + @Autowired + private MetricsProvider metricsProvider; + + @Bean + public Endpoint helloEndpoint() { + EndpointImpl endpoint = new EndpointImpl(bus, new HelloServiceTimedImpl(), null, null, new MetricsFeature[]{ + new MetricsFeature(metricsProvider) + }); + endpoint.publish("/" + HELLO_SERVICE_NAME_V1); + return endpoint; + } + + @Bean + public Endpoint secondHelloEndpoint() { + EndpointImpl endpoint = new EndpointImpl(bus, new HelloServiceEmptyTimedImpl(), null, null, + new MetricsFeature[] { + new MetricsFeature(metricsProvider) + } + ); + endpoint.publish("/" + HELLO_SERVICE_NAME_V2); + return endpoint; + } + + } + + @AfterEach + public void clear() { + registry.clear(); + } + + @Test + public void testJaxwsTimedSuccessMetric() throws MalformedURLException { + // given in setUp + + // when + String actual = sendSoapRequest(DUMMY_REQUEST_BODY, HELLO_SERVICE_NAME_V1); + + // then + assertThat(actual) + .isEqualTo("<ns2:sayHelloResponse xmlns:ns2=\"http://service.ws.sample/\">" + + "<return>Hello, Elan</return>" + + "</ns2:sayHelloResponse>"); + + await() + .atMost(Duration.ofSeconds(1)) + .ignoreException(MeterNotFoundException.class) + .until(() -> registry.get("cxf.server.requests").timers(), not(empty())); + RequiredSearch serverRequestMetrics = registry.get("cxf.server.requests"); + + Map<Object, Object> serverTags = serverRequestMetrics.timer().getId().getTags().stream() + .collect(toMap(Tag::getKey, Tag::getValue)); + + assertThat(serverTags) + .containsOnly( + entry("exception", "None"), + entry("faultCode", "None"), + entry("method", "POST"), + entry("operation", "sayHello"), + entry("uri", "/Service/" + HELLO_SERVICE_NAME_V1), + entry("outcome", "SUCCESS"), + entry("status", "200")); + + RequiredSearch timedMetrics = registry.get("sayHello.requests"); + + Map<Object, Object> timedTags = timedMetrics.timer().getId().getTags().stream() + .collect(toMap(Tag::getKey, Tag::getValue)); + + assertThat(timedTags) + .containsOnly( + entry("exception", "None"), + entry("faultCode", "None"), + entry("method", "POST"), + entry("operation", "sayHello"), + entry("uri", "/Service/" + HELLO_SERVICE_NAME_V1), + entry("outcome", "SUCCESS"), + entry("status", "200")); + + RequiredSearch clientRequestMetrics = registry.get("cxf.client.requests"); + + Map<Object, Object> clientTags = clientRequestMetrics.timer().getId().getTags().stream() + .collect(toMap(Tag::getKey, Tag::getValue)); + + assertThat(clientTags) + .containsOnly( + entry("exception", "None"), + entry("faultCode", "None"), + entry("method", "POST"), + entry("operation", "Invoke"), + entry("uri", "http://localhost:" + port + "/Service/" + HELLO_SERVICE_NAME_V1), + entry("outcome", "SUCCESS"), + entry("status", "200")); + } + + @Test + public void testJaxwsEmptyTimedSuccessMetric() throws MalformedURLException { + // given in setUp + + // when + String actual = sendSoapRequest(DUMMY_REQUEST_BODY, HELLO_SERVICE_NAME_V2); + + // then + assertThat(actual) + .isEqualTo("<ns2:sayHelloResponse xmlns:ns2=\"http://service.ws.sample/\">" + + "<return>Hello, Elan</return>" + + "</ns2:sayHelloResponse>"); + + await() + .atMost(Duration.ofSeconds(1)) + .ignoreException(MeterNotFoundException.class) + .until(() -> registry.get("cxf.server.requests").timers(), not(empty())); + RequiredSearch serverRequestMetrics = registry.get("cxf.server.requests"); + + Map<Object, Object> serverTags = serverRequestMetrics.timer().getId().getTags().stream() + .collect(toMap(Tag::getKey, Tag::getValue)); + + assertThat(serverTags) + .containsOnly( + entry("exception", "None"), + entry("faultCode", "None"), + entry("method", "POST"), + entry("operation", "sayHello"), + entry("uri", "/Service/" + HELLO_SERVICE_NAME_V2), + entry("outcome", "SUCCESS"), + entry("status", "200")); + + RequiredSearch timedMetrics = registry.get("sayHello"); + + Map<Object, Object> timedTags = timedMetrics.timer().getId().getTags().stream() + .collect(toMap(Tag::getKey, Tag::getValue)); + + assertThat(timedTags) + .containsOnly( + entry("exception", "None"), + entry("faultCode", "None"), + entry("method", "POST"), + entry("operation", "sayHello"), + entry("uri", "/Service/" + HELLO_SERVICE_NAME_V2), + entry("outcome", "SUCCESS"), + entry("status", "200")); + + RequiredSearch clientRequestMetrics = registry.get("cxf.client.requests"); + + Map<Object, Object> clientTags = clientRequestMetrics.timer().getId().getTags().stream() + .collect(toMap(Tag::getKey, Tag::getValue)); + + assertThat(clientTags) + .containsOnly( + entry("exception", "None"), + entry("faultCode", "None"), + entry("method", "POST"), + entry("operation", "Invoke"), + entry("uri", "http://localhost:" + port + "/Service/" + HELLO_SERVICE_NAME_V2), + entry("outcome", "SUCCESS"), + entry("status", "200")); + } + + private String sendSoapRequest(String requestBody, final String serviceName) throws MalformedURLException { + String address = "http://localhost:" + port + "/Service/" + serviceName; + + StreamSource source = new StreamSource(new StringReader(requestBody)); + Service service = Service.create(new URL(address + "?wsdl"), + new QName("http://service.ws.sample/", "HelloService"), new MetricsFeature(metricsProvider)); + Dispatch<Source> dispatch = service.createDispatch(new QName("http://service.ws.sample/", "HelloPort"), + Source.class, Mode.PAYLOAD); + + Source result = dispatch.invoke(source); + return StaxUtils.toString(result); + } +}
