From b9cde827058ca11a10a6d36f9b8bedda37b0508a Mon Sep 17 00:00:00 2001 From: Markus Jung Date: Sun, 22 Mar 2026 21:42:09 +0100 Subject: [PATCH 1/2] [CXF-9209] UriInfoImpl#getMatchedResourceTemplate support variable Paths --- .../apache/cxf/jaxrs/impl/UriInfoImpl.java | 32 ++++++---- .../apache/cxf/jaxrs/utils/JAXRSUtils.java | 2 +- .../cxf/jaxrs/impl/UriInfoImplTest.java | 64 +++++++++++++++++++ 3 files changed, 86 insertions(+), 12 deletions(-) diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriInfoImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriInfoImpl.java index 93e3b4e288b..628e057e77b 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriInfoImpl.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriInfoImpl.java @@ -27,6 +27,7 @@ import java.util.Map; import java.util.logging.Logger; +import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.PathSegment; import jakarta.ws.rs.core.UriBuilder; @@ -40,6 +41,7 @@ import org.apache.cxf.jaxrs.utils.JAXRSUtils; import org.apache.cxf.message.Message; import org.apache.cxf.message.MessageUtils; +import org.apache.cxf.transport.http.AbstractHTTPDestination; public class UriInfoImpl implements UriInfo { private static final Logger LOG = LogUtils.getL7dLogger(UriInfoImpl.class); @@ -246,22 +248,30 @@ public URI resolve(URI uri) { @Override public String getMatchedResourceTemplate() { if (stack != null) { - final List templates = new LinkedList<>(); + String matchedResourceTemplate = getBaseUri().getPath(); + + if (HttpUtils.isHttpRequest(message)) { + final HttpServletRequest req = (HttpServletRequest) message.get(AbstractHTTPDestination.HTTP_REQUEST); + matchedResourceTemplate = matchedResourceTemplate.substring(req.getContextPath().length()); + } + for (MethodInvocationInfo invocation : stack) { OperationResourceInfo ori = invocation.getMethodInfo(); - templates.add(ori.getClassResourceInfo().getURITemplate()); - templates.add(ori.getURITemplate()); - } - - if (!templates.isEmpty()) { - UriBuilder builder = UriBuilder.fromPath(templates.get(0).getValue()); - for (int i = 1; i < templates.size(); ++i) { - builder = builder.path(templates.get(i).getValue()); - } - return builder.build().toString(); + + matchedResourceTemplate = JAXRSUtils.combineUriTemplates( + matchedResourceTemplate, getValue(ori.getClassResourceInfo().getURITemplate())); + + matchedResourceTemplate = JAXRSUtils.combineUriTemplates( + matchedResourceTemplate, getValue(ori.getURITemplate())); } + return matchedResourceTemplate; } + LOG.fine("No resource stack information, returning empty template"); return ""; } + + private String getValue(URITemplate uriTemplate) { + return uriTemplate == null ? null : uriTemplate.getValue(); + } } 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..192b11a82c1 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 @@ -2160,7 +2160,7 @@ private static String getUriTemplate(ClassResourceInfo cri) { * @param child child URI template * @return the URI template combined from the parent and child */ - private static String combineUriTemplates(final String parent, final String child) { + public static String combineUriTemplates(final String parent, final String child) { if (StringUtils.isEmpty(child)) { return parent; } diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriInfoImplTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriInfoImplTest.java index ae6c97105b2..2ba797d27f8 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriInfoImplTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriInfoImplTest.java @@ -24,6 +24,7 @@ import java.util.ArrayList; import java.util.List; +import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.core.MultivaluedMap; @@ -41,6 +42,7 @@ import org.apache.cxf.message.Message; import org.apache.cxf.message.MessageImpl; import org.apache.cxf.service.model.EndpointInfo; +import org.apache.cxf.transport.http.AbstractHTTPDestination; import org.apache.cxf.transport.servlet.ServletDestination; import org.junit.Test; @@ -430,6 +432,12 @@ public Response get() { return null; } + @GET + @Path("one/{name:[a-zA-Z][a-zA-Z_0-9]*}") + public Response getTemplate() { + return null; + } + @GET @Path("bar") public Response getSubMethod() { @@ -563,6 +571,62 @@ public void testGetMatchedURIsSubResourceLocatorSubPath() throws Exception { assertEquals("foo", matchedUris.get(2)); } + @Test + public void testGetMatchedResourceTemplateIncludesApplicationPathAndTemplateVariables() throws Exception { + Message m = mockMessage("http://localhost:8080/app", "/foo/one/abc"); + OperationResourceInfoStack oriStack = new OperationResourceInfoStack(); + ClassResourceInfo cri = getCri(RootResource.class, true); + OperationResourceInfo ori = getOri(cri, "getTemplate"); + + MethodInvocationInfo miInfo = new MethodInvocationInfo(ori, RootResource.class, new ArrayList()); + oriStack.push(miInfo); + m.put(OperationResourceInfoStack.class, oriStack); + + UriInfoImpl u = new UriInfoImpl(m); + assertEquals("/app/foo/one/{name:[a-zA-Z][a-zA-Z_0-9]*}", u.getMatchedResourceTemplate()); + } + + @Test + public void testGetMatchedResourceTemplateStripsServletContextPath() throws Exception { + Message m = mockMessage("http://localhost:8080/context/app", "/foo/bar"); + + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getContextPath()).thenReturn("/context"); + m.put(AbstractHTTPDestination.HTTP_REQUEST, req); + + OperationResourceInfoStack oriStack = new OperationResourceInfoStack(); + ClassResourceInfo cri = getCri(RootResource.class, true); + OperationResourceInfo ori = getOri(cri, "getSubMethod"); + + MethodInvocationInfo miInfo = new MethodInvocationInfo(ori, RootResource.class, new ArrayList()); + oriStack.push(miInfo); + m.put(OperationResourceInfoStack.class, oriStack); + + UriInfoImpl u = new UriInfoImpl(m); + assertEquals("/app/foo/bar", u.getMatchedResourceTemplate()); + } + + @Test + public void testGetMatchedResourceTemplateSubResourceWithoutClassPath() throws Exception { + Message m = mockMessage("http://localhost:8080/app", "/foo/sub"); + OperationResourceInfoStack oriStack = new OperationResourceInfoStack(); + ClassResourceInfo rootCri = getCri(RootResource.class, true); + OperationResourceInfo rootOri = getOri(rootCri, "getSubResourceLocator"); + + MethodInvocationInfo miInfo = new MethodInvocationInfo(rootOri, RootResource.class, new ArrayList()); + oriStack.push(miInfo); + + ClassResourceInfo subCri = getCri(SubResource.class, false); + OperationResourceInfo subOri = getOri(subCri, "getFromSub"); + + miInfo = new MethodInvocationInfo(subOri, SubResource.class, new ArrayList()); + oriStack.push(miInfo); + m.put(OperationResourceInfoStack.class, oriStack); + + UriInfoImpl u = new UriInfoImpl(m); + assertEquals("/app/foo/sub", u.getMatchedResourceTemplate()); + } + private Message mockMessage(String baseAddress, String pathInfo) { return mockMessage(baseAddress, pathInfo, null, null); } From d7191bdb95588f9ac338fbf823513e441bff3120 Mon Sep 17 00:00:00 2001 From: Markus Jung Date: Tue, 24 Mar 2026 20:36:03 +0100 Subject: [PATCH 2/2] Read ApplicationPath instead of baseUri substring --- .../apache/cxf/jaxrs/impl/UriInfoImpl.java | 28 +++++++++++++------ .../cxf/jaxrs/impl/UriInfoImplTest.java | 27 ++++++++++++------ 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriInfoImpl.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriInfoImpl.java index 628e057e77b..111091f41cb 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriInfoImpl.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/UriInfoImpl.java @@ -27,21 +27,22 @@ import java.util.Map; import java.util.logging.Logger; -import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.core.Application; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.PathSegment; import jakarta.ws.rs.core.UriBuilder; import jakarta.ws.rs.core.UriInfo; import org.apache.cxf.common.logging.LogUtils; +import org.apache.cxf.jaxrs.model.ApplicationInfo; import org.apache.cxf.jaxrs.model.MethodInvocationInfo; import org.apache.cxf.jaxrs.model.OperationResourceInfo; import org.apache.cxf.jaxrs.model.OperationResourceInfoStack; import org.apache.cxf.jaxrs.model.URITemplate; import org.apache.cxf.jaxrs.utils.HttpUtils; import org.apache.cxf.jaxrs.utils.JAXRSUtils; +import org.apache.cxf.jaxrs.utils.ResourceUtils; import org.apache.cxf.message.Message; import org.apache.cxf.message.MessageUtils; -import org.apache.cxf.transport.http.AbstractHTTPDestination; public class UriInfoImpl implements UriInfo { private static final Logger LOG = LogUtils.getL7dLogger(UriInfoImpl.class); @@ -248,12 +249,7 @@ public URI resolve(URI uri) { @Override public String getMatchedResourceTemplate() { if (stack != null) { - String matchedResourceTemplate = getBaseUri().getPath(); - - if (HttpUtils.isHttpRequest(message)) { - final HttpServletRequest req = (HttpServletRequest) message.get(AbstractHTTPDestination.HTTP_REQUEST); - matchedResourceTemplate = matchedResourceTemplate.substring(req.getContextPath().length()); - } + String matchedResourceTemplate = getApplicationPath(); for (MethodInvocationInfo invocation : stack) { OperationResourceInfo ori = invocation.getMethodInfo(); @@ -274,4 +270,20 @@ public String getMatchedResourceTemplate() { private String getValue(URITemplate uriTemplate) { return uriTemplate == null ? null : uriTemplate.getValue(); } + + private String getApplicationPath() { + ApplicationInfo appInfo = (ApplicationInfo) message.getExchange().getEndpoint() + .get(Application.class.getName()); + + if (appInfo == null) { + return "/"; + } + + String applicationPath = ResourceUtils.locateApplicationPath(appInfo.getProvider().getClass()).value(); + if (!applicationPath.startsWith("/")) { + applicationPath = "/" + applicationPath; + } + + return applicationPath; + } } diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriInfoImplTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriInfoImplTest.java index 2ba797d27f8..d419beac4b4 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriInfoImplTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/impl/UriInfoImplTest.java @@ -24,13 +24,16 @@ import java.util.ArrayList; import java.util.List; -import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Application; import jakarta.ws.rs.core.MultivaluedMap; import jakarta.ws.rs.core.PathSegment; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.UriInfo; +import org.apache.cxf.endpoint.Endpoint; +import org.apache.cxf.jaxrs.model.ApplicationInfo; import org.apache.cxf.jaxrs.model.ClassResourceInfo; import org.apache.cxf.jaxrs.model.MethodInvocationInfo; import org.apache.cxf.jaxrs.model.OperationResourceInfo; @@ -42,7 +45,6 @@ import org.apache.cxf.message.Message; import org.apache.cxf.message.MessageImpl; import org.apache.cxf.service.model.EndpointInfo; -import org.apache.cxf.transport.http.AbstractHTTPDestination; import org.apache.cxf.transport.servlet.ServletDestination; import org.junit.Test; @@ -463,6 +465,10 @@ public Response getFromSubSub() { } } + @ApplicationPath("app") + public static class TestApplication extends Application { + } + private static ClassResourceInfo getCri(Class clazz, boolean setUriTemplate) { ClassResourceInfo cri = new ClassResourceInfo(clazz); Path path = AnnotationUtils.getClassAnnotation(clazz, Path.class); @@ -574,6 +580,7 @@ public void testGetMatchedURIsSubResourceLocatorSubPath() throws Exception { @Test public void testGetMatchedResourceTemplateIncludesApplicationPathAndTemplateVariables() throws Exception { Message m = mockMessage("http://localhost:8080/app", "/foo/one/abc"); + setApplication(m, new TestApplication()); OperationResourceInfoStack oriStack = new OperationResourceInfoStack(); ClassResourceInfo cri = getCri(RootResource.class, true); OperationResourceInfo ori = getOri(cri, "getTemplate"); @@ -587,12 +594,9 @@ public void testGetMatchedResourceTemplateIncludesApplicationPathAndTemplateVari } @Test - public void testGetMatchedResourceTemplateStripsServletContextPath() throws Exception { - Message m = mockMessage("http://localhost:8080/context/app", "/foo/bar"); - - HttpServletRequest req = mock(HttpServletRequest.class); - when(req.getContextPath()).thenReturn("/context"); - m.put(AbstractHTTPDestination.HTTP_REQUEST, req); + public void testGetMatchedResourceTemplateUsesApplicationPathAnnotation() throws Exception { + Message m = mockMessage("http://localhost:8080/context/service/app", "/foo/bar"); + setApplication(m, new TestApplication()); OperationResourceInfoStack oriStack = new OperationResourceInfoStack(); ClassResourceInfo cri = getCri(RootResource.class, true); @@ -609,6 +613,7 @@ public void testGetMatchedResourceTemplateStripsServletContextPath() throws Exce @Test public void testGetMatchedResourceTemplateSubResourceWithoutClassPath() throws Exception { Message m = mockMessage("http://localhost:8080/app", "/foo/sub"); + setApplication(m, new TestApplication()); OperationResourceInfoStack oriStack = new OperationResourceInfoStack(); ClassResourceInfo rootCri = getCri(RootResource.class, true); OperationResourceInfo rootOri = getOri(rootCri, "getSubResourceLocator"); @@ -631,6 +636,12 @@ private Message mockMessage(String baseAddress, String pathInfo) { return mockMessage(baseAddress, pathInfo, null, null); } + private void setApplication(Message m, Application app) { + Endpoint endpoint = mock(Endpoint.class); + when(endpoint.get(Application.class.getName())).thenReturn(new ApplicationInfo(app, null)); + m.getExchange().put(Endpoint.class, endpoint); + } + private Message mockMessage(String baseAddress, String pathInfo, String query) { return mockMessage(baseAddress, pathInfo, query, null); }