From b9cde827058ca11a10a6d36f9b8bedda37b0508a Mon Sep 17 00:00:00 2001 From: Markus Jung Date: Sun, 22 Mar 2026 21:42:09 +0100 Subject: [PATCH 1/4] [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/4] 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); } From c936f602aa3e0606ef87cbf9a96f65d3d0d27181 Mon Sep 17 00:00:00 2001 From: Markus Jung Date: Wed, 25 Mar 2026 08:39:10 +0100 Subject: [PATCH 3/4] read effective applicationPath from new property in Exchange --- .../apache/cxf/jaxrs/impl/UriInfoImpl.java | 22 ++-------------- .../apache/cxf/jaxrs/utils/JAXRSUtils.java | 2 ++ .../apache/cxf/jaxrs/utils/ResourceUtils.java | 1 + .../cxf/jaxrs/impl/UriInfoImplTest.java | 26 +++++++------------ .../cxf/jaxrs/utils/ResourceUtilsTest.java | 13 +++++++++- 5 files changed, 27 insertions(+), 37 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 111091f41cb..259cd50ca05 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,20 +27,17 @@ import java.util.Map; import java.util.logging.Logger; -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; @@ -249,7 +246,8 @@ public URI resolve(URI uri) { @Override public String getMatchedResourceTemplate() { if (stack != null) { - String matchedResourceTemplate = getApplicationPath(); + String matchedResourceTemplate = (String) message.getExchange().getEndpoint() + .get(JAXRSUtils.MATCHED_RESOURCE_TEMPLATE_BASE_PATH); for (MethodInvocationInfo invocation : stack) { OperationResourceInfo ori = invocation.getMethodInfo(); @@ -270,20 +268,4 @@ 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/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java index 192b11a82c1..2d9dced27a0 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 @@ -157,6 +157,8 @@ public final class JAXRSUtils { public static final String ROOT_PROVIDER = "service.root.provider"; public static final String EXCEPTION_FROM_MAPPER = "exception.from.mapper"; public static final String SECOND_JAXRS_EXCEPTION = "second.jaxrs.exception"; + public static final String MATCHED_RESOURCE_TEMPLATE_BASE_PATH = + "org.apache.cxf.jaxrs.matched-resource-template-base-path"; public static final String PARTIAL_HIERARCHICAL_MEDIA_SUBTYPE_CHECK = "media.subtype.partial.check"; public static final String DOC_LOCATION = "wadl.location"; diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java index 1355d1a1b44..7740b4db3c5 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java @@ -965,6 +965,7 @@ public static JAXRSServerFactoryBean createApplication(Application app, bean.getProperties(true).putAll(appProps); } bean.setApplication(app); + bean.getProperties(true).put(JAXRSUtils.MATCHED_RESOURCE_TEMPLATE_BASE_PATH, address); return bean; } 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 d419beac4b4..68e74048591 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,22 +24,20 @@ import java.util.ArrayList; import java.util.List; -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; import org.apache.cxf.jaxrs.model.OperationResourceInfoStack; import org.apache.cxf.jaxrs.model.URITemplate; import org.apache.cxf.jaxrs.utils.AnnotationUtils; +import org.apache.cxf.jaxrs.utils.JAXRSUtils; import org.apache.cxf.message.Exchange; import org.apache.cxf.message.ExchangeImpl; import org.apache.cxf.message.Message; @@ -465,10 +463,6 @@ 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); @@ -580,7 +574,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()); + setApplicationPath(m, "/app"); OperationResourceInfoStack oriStack = new OperationResourceInfoStack(); ClassResourceInfo cri = getCri(RootResource.class, true); OperationResourceInfo ori = getOri(cri, "getTemplate"); @@ -594,9 +588,9 @@ public void testGetMatchedResourceTemplateIncludesApplicationPathAndTemplateVari } @Test - public void testGetMatchedResourceTemplateUsesApplicationPathAnnotation() throws Exception { - Message m = mockMessage("http://localhost:8080/context/service/app", "/foo/bar"); - setApplication(m, new TestApplication()); + public void testGetMatchedResourceTemplateIgnoresPathBeforeApplicationPath() throws Exception { + Message m = mockMessage("http://localhost:8080/context/service", "/foo/bar"); + setApplicationPath(m, "/service"); OperationResourceInfoStack oriStack = new OperationResourceInfoStack(); ClassResourceInfo cri = getCri(RootResource.class, true); @@ -607,13 +601,13 @@ public void testGetMatchedResourceTemplateUsesApplicationPathAnnotation() throws m.put(OperationResourceInfoStack.class, oriStack); UriInfoImpl u = new UriInfoImpl(m); - assertEquals("/app/foo/bar", u.getMatchedResourceTemplate()); + assertEquals("/service/foo/bar", u.getMatchedResourceTemplate()); } @Test public void testGetMatchedResourceTemplateSubResourceWithoutClassPath() throws Exception { Message m = mockMessage("http://localhost:8080/app", "/foo/sub"); - setApplication(m, new TestApplication()); + setApplicationPath(m, "/"); OperationResourceInfoStack oriStack = new OperationResourceInfoStack(); ClassResourceInfo rootCri = getCri(RootResource.class, true); OperationResourceInfo rootOri = getOri(rootCri, "getSubResourceLocator"); @@ -629,16 +623,16 @@ public void testGetMatchedResourceTemplateSubResourceWithoutClassPath() throws E m.put(OperationResourceInfoStack.class, oriStack); UriInfoImpl u = new UriInfoImpl(m); - assertEquals("/app/foo/sub", u.getMatchedResourceTemplate()); + assertEquals("/foo/sub", u.getMatchedResourceTemplate()); } private Message mockMessage(String baseAddress, String pathInfo) { return mockMessage(baseAddress, pathInfo, null, null); } - private void setApplication(Message m, Application app) { + private void setApplicationPath(Message m, String applicationPath) { Endpoint endpoint = mock(Endpoint.class); - when(endpoint.get(Application.class.getName())).thenReturn(new ApplicationInfo(app, null)); + when(endpoint.get(JAXRSUtils.MATCHED_RESOURCE_TEMPLATE_BASE_PATH)).thenReturn(applicationPath); m.getExchange().put(Endpoint.class, endpoint); } diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java index 7d2d1e75fe1..45089b11688 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java @@ -333,6 +333,7 @@ public void shouldCreateApplicationWhichInheritsApplicationPath() throws Excepti JAXRSServerFactoryBean application = ResourceUtils.createApplication( new SuperApplication(), false, false, false, null); assertEquals("/base", application.getAddress()); + assertEquals("/base", application.getProperties().get(JAXRSUtils.MATCHED_RESOURCE_TEMPLATE_BASE_PATH)); } @Test @@ -340,6 +341,16 @@ public void shouldCreateApplicationWhichOverridesApplicationPath() throws Except JAXRSServerFactoryBean application = ResourceUtils.createApplication( new CustomApplication(), false, false, false, null); assertEquals("/custom", application.getAddress()); + assertEquals("/custom", + application.getProperties().get(JAXRSUtils.MATCHED_RESOURCE_TEMPLATE_BASE_PATH)); + } + + @Test + public void shouldCreateApplicationWhichIgnoresApplicationPath() throws Exception { + JAXRSServerFactoryBean application = ResourceUtils.createApplication( + new SuperApplication(), true, false, false, null); + assertEquals("/", application.getAddress()); + assertEquals("/", application.getProperties().get(JAXRSUtils.MATCHED_RESOURCE_TEMPLATE_BASE_PATH)); } public interface IProductResource { @@ -446,4 +457,4 @@ private static final class SuperApplication extends BaseApplication { private static final class CustomApplication extends BaseApplication { } -} \ No newline at end of file +} From b8125bec39ed6cecb2afdd1a736a9046d83d3de5 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Sat, 4 Apr 2026 09:28:14 -0400 Subject: [PATCH 4/4] Use builder.toTemplate() for UriInfo#getMatchedResourceTemplate() implementantion Signed-off-by: Andriy Redko --- .../apache/cxf/jaxrs/impl/UriInfoImpl.java | 29 +++++++++---------- .../apache/cxf/jaxrs/utils/JAXRSUtils.java | 4 +-- .../apache/cxf/jaxrs/utils/ResourceUtils.java | 1 - .../cxf/jaxrs/impl/UriInfoImplTest.java | 17 ++--------- .../cxf/jaxrs/utils/ResourceUtilsTest.java | 13 +-------- .../systest/jaxrs/BookStorePerRequest.java | 7 +++++ .../JAXRSClientServerNonSpringBookTest.java | 10 +++++++ 7 files changed, 36 insertions(+), 45 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 259cd50ca05..b4e99347ccc 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 @@ -246,26 +246,25 @@ public URI resolve(URI uri) { @Override public String getMatchedResourceTemplate() { if (stack != null) { - String matchedResourceTemplate = (String) message.getExchange().getEndpoint() - .get(JAXRSUtils.MATCHED_RESOURCE_TEMPLATE_BASE_PATH); - + final List templates = new LinkedList<>(); for (MethodInvocationInfo invocation : stack) { OperationResourceInfo ori = invocation.getMethodInfo(); - - matchedResourceTemplate = JAXRSUtils.combineUriTemplates( - matchedResourceTemplate, getValue(ori.getClassResourceInfo().getURITemplate())); - - matchedResourceTemplate = JAXRSUtils.combineUriTemplates( - matchedResourceTemplate, getValue(ori.getURITemplate())); + final URITemplate classUriTemplate = ori.getClassResourceInfo().getURITemplate(); + if (classUriTemplate != null) { + templates.add(classUriTemplate); + } + 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.toTemplate(); } - 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 2d9dced27a0..095de19b982 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 @@ -157,8 +157,6 @@ public final class JAXRSUtils { public static final String ROOT_PROVIDER = "service.root.provider"; public static final String EXCEPTION_FROM_MAPPER = "exception.from.mapper"; public static final String SECOND_JAXRS_EXCEPTION = "second.jaxrs.exception"; - public static final String MATCHED_RESOURCE_TEMPLATE_BASE_PATH = - "org.apache.cxf.jaxrs.matched-resource-template-base-path"; public static final String PARTIAL_HIERARCHICAL_MEDIA_SUBTYPE_CHECK = "media.subtype.partial.check"; public static final String DOC_LOCATION = "wadl.location"; @@ -2162,7 +2160,7 @@ private static String getUriTemplate(ClassResourceInfo cri) { * @param child child URI template * @return the URI template combined from the parent and child */ - public static String combineUriTemplates(final String parent, final String child) { + private static String combineUriTemplates(final String parent, final String child) { if (StringUtils.isEmpty(child)) { return parent; } diff --git a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java index 7740b4db3c5..1355d1a1b44 100644 --- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java +++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ResourceUtils.java @@ -965,7 +965,6 @@ public static JAXRSServerFactoryBean createApplication(Application app, bean.getProperties(true).putAll(appProps); } bean.setApplication(app); - bean.getProperties(true).put(JAXRSUtils.MATCHED_RESOURCE_TEMPLATE_BASE_PATH, address); return bean; } 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 68e74048591..bfc5c8ba871 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 @@ -30,14 +30,12 @@ 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.ClassResourceInfo; 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.AnnotationUtils; -import org.apache.cxf.jaxrs.utils.JAXRSUtils; import org.apache.cxf.message.Exchange; import org.apache.cxf.message.ExchangeImpl; import org.apache.cxf.message.Message; @@ -574,7 +572,6 @@ public void testGetMatchedURIsSubResourceLocatorSubPath() throws Exception { @Test public void testGetMatchedResourceTemplateIncludesApplicationPathAndTemplateVariables() throws Exception { Message m = mockMessage("http://localhost:8080/app", "/foo/one/abc"); - setApplicationPath(m, "/app"); OperationResourceInfoStack oriStack = new OperationResourceInfoStack(); ClassResourceInfo cri = getCri(RootResource.class, true); OperationResourceInfo ori = getOri(cri, "getTemplate"); @@ -584,13 +581,12 @@ public void testGetMatchedResourceTemplateIncludesApplicationPathAndTemplateVari 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()); + assertEquals("/foo/one/{name:[a-zA-Z][a-zA-Z_0-9]*}", u.getMatchedResourceTemplate()); } @Test public void testGetMatchedResourceTemplateIgnoresPathBeforeApplicationPath() throws Exception { Message m = mockMessage("http://localhost:8080/context/service", "/foo/bar"); - setApplicationPath(m, "/service"); OperationResourceInfoStack oriStack = new OperationResourceInfoStack(); ClassResourceInfo cri = getCri(RootResource.class, true); @@ -601,13 +597,12 @@ public void testGetMatchedResourceTemplateIgnoresPathBeforeApplicationPath() thr m.put(OperationResourceInfoStack.class, oriStack); UriInfoImpl u = new UriInfoImpl(m); - assertEquals("/service/foo/bar", u.getMatchedResourceTemplate()); + assertEquals("/foo/bar", u.getMatchedResourceTemplate()); } @Test public void testGetMatchedResourceTemplateSubResourceWithoutClassPath() throws Exception { Message m = mockMessage("http://localhost:8080/app", "/foo/sub"); - setApplicationPath(m, "/"); OperationResourceInfoStack oriStack = new OperationResourceInfoStack(); ClassResourceInfo rootCri = getCri(RootResource.class, true); OperationResourceInfo rootOri = getOri(rootCri, "getSubResourceLocator"); @@ -623,19 +618,13 @@ public void testGetMatchedResourceTemplateSubResourceWithoutClassPath() throws E m.put(OperationResourceInfoStack.class, oriStack); UriInfoImpl u = new UriInfoImpl(m); - assertEquals("/foo/sub", u.getMatchedResourceTemplate()); + assertEquals("/foo/sub/", u.getMatchedResourceTemplate()); } private Message mockMessage(String baseAddress, String pathInfo) { return mockMessage(baseAddress, pathInfo, null, null); } - private void setApplicationPath(Message m, String applicationPath) { - Endpoint endpoint = mock(Endpoint.class); - when(endpoint.get(JAXRSUtils.MATCHED_RESOURCE_TEMPLATE_BASE_PATH)).thenReturn(applicationPath); - m.getExchange().put(Endpoint.class, endpoint); - } - private Message mockMessage(String baseAddress, String pathInfo, String query) { return mockMessage(baseAddress, pathInfo, query, null); } diff --git a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java index 45089b11688..7d2d1e75fe1 100644 --- a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java +++ b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/utils/ResourceUtilsTest.java @@ -333,7 +333,6 @@ public void shouldCreateApplicationWhichInheritsApplicationPath() throws Excepti JAXRSServerFactoryBean application = ResourceUtils.createApplication( new SuperApplication(), false, false, false, null); assertEquals("/base", application.getAddress()); - assertEquals("/base", application.getProperties().get(JAXRSUtils.MATCHED_RESOURCE_TEMPLATE_BASE_PATH)); } @Test @@ -341,16 +340,6 @@ public void shouldCreateApplicationWhichOverridesApplicationPath() throws Except JAXRSServerFactoryBean application = ResourceUtils.createApplication( new CustomApplication(), false, false, false, null); assertEquals("/custom", application.getAddress()); - assertEquals("/custom", - application.getProperties().get(JAXRSUtils.MATCHED_RESOURCE_TEMPLATE_BASE_PATH)); - } - - @Test - public void shouldCreateApplicationWhichIgnoresApplicationPath() throws Exception { - JAXRSServerFactoryBean application = ResourceUtils.createApplication( - new SuperApplication(), true, false, false, null); - assertEquals("/", application.getAddress()); - assertEquals("/", application.getProperties().get(JAXRSUtils.MATCHED_RESOURCE_TEMPLATE_BASE_PATH)); } public interface IProductResource { @@ -457,4 +446,4 @@ private static final class SuperApplication extends BaseApplication { private static final class CustomApplication extends BaseApplication { } -} +} \ No newline at end of file diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookStorePerRequest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookStorePerRequest.java index 4ed0cd0ede0..e9ab093329b 100644 --- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookStorePerRequest.java +++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookStorePerRequest.java @@ -31,6 +31,7 @@ import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.HttpHeaders; import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; @Path("/bookstore2") public class BookStorePerRequest { @@ -86,6 +87,12 @@ public void setHttpHeaders(HttpHeaders headers) { public Book getBookByHeader2() throws Exception { return getBookByHeader(); } + + @GET + @Path("/book%20template/{name:[a-zA-Z][a-zA-Z_0-9]*}") + public String getBookTemplate(@Context final UriInfo info) throws Exception { + return info.getMatchedResourceTemplate(); + } @GET @Path("/bookheaders/") diff --git a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerNonSpringBookTest.java b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerNonSpringBookTest.java index 3278e4d7d2e..cd19a68b979 100644 --- a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerNonSpringBookTest.java +++ b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSClientServerNonSpringBookTest.java @@ -44,6 +44,8 @@ import org.junit.BeforeClass; import org.junit.Test; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; @@ -202,6 +204,14 @@ public void testGetBook123TwoApplications() throws Exception { doTestPerRequest("http://localhost:" + PORT + "/application6/thebooks/bookstore2/bookheaders"); doTestPerRequest("http://localhost:" + PORT + "/application6/the%20books2/bookstore2/book%20headers"); } + + @Test + public void testGetMatchedResourceTemplate() throws Exception { + WebClient wc = WebClient.create("http://localhost:" + PORT + + "/application6/the%20books2/bookstore2/book%20template/abc"); + assertThat(wc.accept("*/*").get(String.class), + equalTo("/bookstore2/book%20template/{name:[a-zA-Z][a-zA-Z_0-9]*}")); + } @SafeVarargs private final Response doTestPerRequest(String address, Map.Entry ... params) throws Exception {