This is an automated email from the ASF dual-hosted git repository. reta pushed a commit to branch 4.1.x-fixes in repository https://gitbox.apache.org/repos/asf/cxf.git
commit c3993a0a2d3dbc45c159050739791db4a7f81bc5 Author: Andriy Redko <[email protected]> AuthorDate: Tue May 12 22:40:53 2026 -0400 CXF-9211: [JAX-RS proxy clients] Use template URL instead of actual path values for JAX-RS micrometer metrics (#3100) (cherry picked from commit 99b0a407dc85f754de45185b5a2ab25d527ac277) --- .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java | 19 +++++++++- .../apache/cxf/jaxrs/client/ClientProxyImpl.java | 2 + .../spring/boot/SpringJaxrsApplicationTest.java | 44 +++++++++++++++++++++- .../systest/jaxrs/spring/boot/SpringJaxrsTest.java | 2 +- 4 files changed, 63 insertions(+), 4 deletions(-) diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java index 095de19b982..d1beb0c25c8 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java @@ -47,6 +47,7 @@ import java.util.TreeMap; import java.util.function.Supplier; import java.util.logging.Level; import java.util.logging.Logger; +import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.xml.namespace.QName; @@ -206,6 +207,7 @@ public final class JAXRSUtils { "java.util.concurrent.CompletionStage" )); private static final LazyLoadedClass DATA_SOURCE_CLASS = new LazyLoadedClass("jakarta.activation.DataSource"); + private static final Pattern URI_SCHEME = Pattern.compile("^([a-zA-Z]{2,20}):\\/\\/", Pattern.CASE_INSENSITIVE); // Class to lazily call the ClassLoaderUtil.loadClass, but do it once // and cache the result. Then use the class to create instances as needed. @@ -2114,14 +2116,27 @@ public final class JAXRSUtils { String template = basePath; if (StringUtils.isEmpty(template)) { template = "/"; - } else if (!template.startsWith("/")) { + } else if (!template.startsWith("/") && !hasScheme(template)) { template = "/" + template; } template = combineUriTemplates(template, classPathTemplate); return combineUriTemplates(template, methodPathTemplate); } - + + /** + * Checks if the URI string is absolute with the scheme + * @param uriStr URI string + * @return "true" if the URI string is absolute with the scheme, "false" otherwise + */ + private static boolean hasScheme(String uriStr) { + if (uriStr == null) { + return false; + } else { + return URI_SCHEME.matcher(uriStr).find(); + } + } + /** * Gets the URI template of the operation from its resource info * to assemble final URI template diff --git a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java index 467af934c8b..3de1e6239be 100644 --- a/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java +++ b/rt/rs/client/src/main/java/org/apache/cxf/jaxrs/client/ClientProxyImpl.java @@ -88,6 +88,7 @@ import org.apache.cxf.jaxrs.model.ClassResourceInfo; import org.apache.cxf.jaxrs.model.OperationResourceInfo; import org.apache.cxf.jaxrs.model.Parameter; import org.apache.cxf.jaxrs.model.ParameterType; +import org.apache.cxf.jaxrs.model.URITemplate; import org.apache.cxf.jaxrs.utils.AnnotationUtils; import org.apache.cxf.jaxrs.utils.FormUtils; import org.apache.cxf.jaxrs.utils.InjectionUtils; @@ -919,6 +920,7 @@ public class ClientProxyImpl extends AbstractClient implements outMessage.put(PROXY_METHOD_PARAM_BODY_INDEX, bodyIndex); } outMessage.getInterceptorChain().add(bodyWriter); + outMessage.put(URITemplate.URI_TEMPLATE, JAXRSUtils.getUriTemplate(outMessage, cri, ori)); Map<String, Object> reqContext = getRequestContext(outMessage); reqContext.put(OperationResourceInfo.class.getName(), ori); diff --git a/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringJaxrsApplicationTest.java b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringJaxrsApplicationTest.java index 880d4354579..d4cb22a7700 100644 --- a/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringJaxrsApplicationTest.java +++ b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringJaxrsApplicationTest.java @@ -21,6 +21,7 @@ package org.apache.cxf.systest.jaxrs.spring.boot; import java.time.Duration; import java.util.Arrays; +import java.util.Collection; import java.util.Map; import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider; @@ -416,7 +417,7 @@ public class SpringJaxrsApplicationTest { entry("exception", "None"), entry("method", "GET"), entry("operation", "getBook"), - entry("uri", "http://localhost:" + port + "/api/app/library/100"), + entry("uri", "http://localhost:" + port + "/api/app/library/{id}"), entry("outcome", "CLIENT_ERROR"), entry("status", "404")); } @@ -449,10 +450,51 @@ public class SpringJaxrsApplicationTest { entry("status", "UNKNOWN")); } + @Test + public void testJaxrsProxySubresourceSuccessMetric() { + final LibraryApi api = createApi(port); + + final Collection<Book> books = api.catalog().getCatalog("cxf"); + assertThat(books).hasSize(1); + + 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("method", "GET"), + entry("operation", "catalog"), + entry("uri", "/api/app/library/catalog/{catalog}"), + 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("method", "GET"), + entry("operation", "getCatalog"), + entry("uri", "http://localhost:" + port + "/api/app/library/catalog/{catalog}"), + entry("outcome", "SUCCESS"), + entry("status", "200")); + } + private LibraryApi createApi(int portToUse) { final JAXRSClientFactoryBean factory = new JAXRSClientFactoryBean(); factory.setAddress("http://localhost:" + portToUse + "/api/app/library"); factory.setFeatures(Arrays.asList(new MetricsFeature(metricsProvider))); + factory.setProvider(new JacksonJsonProvider()); factory.setResourceClass(LibraryApi.class); return factory.create(LibraryApi.class); } diff --git a/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringJaxrsTest.java b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringJaxrsTest.java index 84bb59bb7d6..5feef3ac73b 100644 --- a/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringJaxrsTest.java +++ b/systests/spring-boot/src/test/java/org/apache/cxf/systest/jaxrs/spring/boot/SpringJaxrsTest.java @@ -386,7 +386,7 @@ public class SpringJaxrsTest { entry("exception", "None"), entry("method", "GET"), entry("operation", "getBook"), - entry("uri", "http://localhost:" + port + "/api/library/100"), + entry("uri", "http://localhost:" + port + "/api/library/{id}"), entry("outcome", "CLIENT_ERROR"), entry("status", "404")); }
