implementation of UriInfo#getMatchedResourceTemplate(s) & API JAX-B drop (#5575)

* implementation of UriInfo#getMatchedResourceTemplate
* Additional modification for Jakarta REST 4.0 API
Signed-off-by: jansupol <jan.supol@oracle.com>
Co-authored-by: Maxim Nesen <maxim.nesen@oracle.com>
diff --git a/core-server/src/main/java/org/glassfish/jersey/server/internal/routing/UriRoutingContext.java b/core-server/src/main/java/org/glassfish/jersey/server/internal/routing/UriRoutingContext.java
index 40faecc..f7a9875 100644
--- a/core-server/src/main/java/org/glassfish/jersey/server/internal/routing/UriRoutingContext.java
+++ b/core-server/src/main/java/org/glassfish/jersey/server/internal/routing/UriRoutingContext.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2024 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -28,6 +28,8 @@
 import java.util.regex.MatchResult;
 import java.util.stream.Collectors;
 
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.core.Application;
 import jakarta.ws.rs.core.MultivaluedHashMap;
 import jakarta.ws.rs.core.MultivaluedMap;
 import jakarta.ws.rs.core.PathSegment;
@@ -36,6 +38,7 @@
 import org.glassfish.jersey.internal.util.collection.ImmutableMultivaluedMap;
 import org.glassfish.jersey.message.internal.TracingLogger;
 import org.glassfish.jersey.server.ContainerRequest;
+import org.glassfish.jersey.server.ResourceConfig;
 import org.glassfish.jersey.server.internal.ServerTraceEvent;
 import org.glassfish.jersey.server.internal.process.Endpoint;
 import org.glassfish.jersey.server.model.Resource;
@@ -245,6 +248,40 @@
         return getMatchedURIs(true);
     }
 
+//@Override
+    public String getMatchedResourceTemplate() {
+        final StringBuilder sb = new StringBuilder();
+        for (String template : getMatchedResourceTemplates()) {
+            sb.append(template.trim());
+        }
+        return sb.toString();
+    }
+
+//@Override
+    public List<String> getMatchedResourceTemplates() {
+        final List<String> list = new ArrayList<>();
+        if (ResourceConfig.class.isInstance(requestContext.getConfiguration())) {
+            Application app = ((ResourceConfig) requestContext.getConfiguration()).getApplication();
+            while (ResourceConfig.class.isInstance(app) && ((ResourceConfig) app).getApplication() != app) {
+                app = ((ResourceConfig) app).getApplication();
+            }
+            final ApplicationPath annotation = app.getClass().getAnnotation(ApplicationPath.class);
+            if (annotation != null) {
+                String value = annotation.value();
+                list.add(value.endsWith("/") ? value.substring(0, value.length() - 1) : value);
+            }
+        }
+
+        final Iterator<UriTemplate> templateIt = templates.descendingIterator();
+        while (templateIt.hasNext()) {
+            String template = templateIt.next().getTemplate().trim();
+            if (!template.equals("/") || list.isEmpty()) { // !subresourceLocator
+                list.add(template);
+            }
+        }
+        return list;
+    }
+
     private static final Function<String, String> PATH_DECODER = input -> UriComponent.decode(input, UriComponent.Type.PATH);
 
     @Override
diff --git a/core-server/src/test/java/org/glassfish/jersey/server/GetMatchedResourceTemplateTest.java b/core-server/src/test/java/org/glassfish/jersey/server/GetMatchedResourceTemplateTest.java
new file mode 100644
index 0000000..8ba37ad
--- /dev/null
+++ b/core-server/src/test/java/org/glassfish/jersey/server/GetMatchedResourceTemplateTest.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.server;
+
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.HttpMethod;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.UriInfo;
+import org.glassfish.jersey.server.internal.routing.UriRoutingContext;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.concurrent.ExecutionException;
+
+public class GetMatchedResourceTemplateTest {
+
+    public static final String ONE_POST = "{name:[a-zA-Z][a-zA-Z_0-9]*}";
+    public static final String TWO_GET = "{Prefix}{p:/?}{id: ((\\d+)?)}/abc{p2:/?}{number: (([A-Za-z0-9]*)?)}";
+    public static final String TWO_POST = "{Prefix}{p:/?}{id: ((\\d+)?)}/abc/{yeah}";
+    public static final String THREE_SUB = "{x:[a-z]}";
+
+    public static class SubGet {
+        @PUT
+        public String get(@Context UriInfo uriInfo) {
+            return ((UriRoutingContext) uriInfo).getMatchedResourceTemplate();
+        }
+    }
+
+    @Path("one")
+    public static class ResourceOne {
+        @POST
+        @Path(ONE_POST)
+        public Response post(@Context UriInfo info) {
+            return Response.ok(((UriRoutingContext) info).getMatchedResourceTemplate()).build();
+        }
+    }
+
+    @Path("two")
+    public static class ResourceTwo {
+        @GET
+        @Path(TWO_GET)
+        public Response get(@Context UriInfo info) {
+            return Response.ok(((UriRoutingContext) info).getMatchedResourceTemplate()).build();
+        }
+
+        @POST
+        @Path(TWO_POST)
+        public Response post(@Context UriInfo info) {
+            return Response.ok(((UriRoutingContext) info).getMatchedResourceTemplate()).build();
+        }
+    }
+
+    @Path("three")
+    public static class ResourceThree {
+        @Path(THREE_SUB)
+        public SubGet doAnything4() {
+            return new SubGet();
+        }
+    }
+
+    @Path("four")
+    public static class ResourceFour {
+        @Path(THREE_SUB)
+        public ResourceOne postOne() {
+            return new ResourceOne();
+        }
+    }
+
+    @Test
+    public void testPOSTone() throws ExecutionException, InterruptedException {
+        ApplicationHandler applicationHandler = new ApplicationHandler(new ResourceConfig(ResourceOne.class));
+        Object entity = applicationHandler.apply(RequestContextBuilder.from("/one/azazaz00", HttpMethod.POST)
+                .build()).get().getEntity();
+        Assertions.assertEquals("/one/" + ONE_POST, entity);
+    }
+
+    @Test
+    public void testTWOget() throws ExecutionException, InterruptedException {
+        ApplicationHandler applicationHandler = new ApplicationHandler(new ResourceConfig(ResourceTwo.class));
+        Object entity = applicationHandler.apply(RequestContextBuilder.from("/two/P/abc/MyNumber", HttpMethod.GET)
+                .build()).get().getEntity();
+        Assertions.assertEquals("/two/" + TWO_GET, entity);
+    }
+
+    @Test
+    public void testTWOpost() throws ExecutionException, InterruptedException {
+        ApplicationHandler applicationHandler = new ApplicationHandler(new ResourceConfig(ResourceTwo.class));
+        Object entity = applicationHandler.apply(RequestContextBuilder.from("/two/P/abc/MyNumber", HttpMethod.POST)
+                .build()).get().getEntity();
+        Assertions.assertEquals("/two/" + TWO_POST, entity);
+    }
+
+    @Test
+    public void testPUTthree() throws ExecutionException, InterruptedException {
+        ApplicationHandler applicationHandler = new ApplicationHandler(new ResourceConfig(ResourceThree.class));
+        Object entity = applicationHandler.apply(RequestContextBuilder.from("/three/a", HttpMethod.PUT)
+                .build()).get().getEntity();
+        Assertions.assertEquals("/three/" + THREE_SUB, entity);
+    }
+
+    @Test
+    public void testPOSTfour() throws ExecutionException, InterruptedException {
+        ApplicationHandler applicationHandler = new ApplicationHandler(new ResourceConfig(ResourceFour.class));
+        Object entity = applicationHandler.apply(RequestContextBuilder.from("/four/a/azazaz00", HttpMethod.POST)
+                .build()).get().getEntity();
+        Assertions.assertEquals("/four/" + THREE_SUB + "/" + ONE_POST, entity);
+    }
+}
diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemRepresentation.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemRepresentation.java
index bfaeaa1..7e8997b 100644
--- a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemRepresentation.java
+++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemRepresentation.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0, which is available at
@@ -70,7 +70,7 @@
             bindings = @Binding(name = "id", value = "${instance.id}"),
             rel = "self"
     )
-    @XmlJavaTypeAdapter(Link.JaxbAdapter.class)
+    @XmlJavaTypeAdapter(JaxbAdapter.class)
     @XmlElement(name = "link")
     Link self;
 
@@ -91,7 +91,7 @@
             )})
     @XmlElement(name = "link")
     @XmlElementWrapper(name = "links")
-    @XmlJavaTypeAdapter(Link.JaxbAdapter.class)
+    @XmlJavaTypeAdapter(JaxbAdapter.class)
     List<Link> links;
 
     public ItemRepresentation() {
diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemsRepresentation.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemsRepresentation.java
index 4d702a2..aecc983 100644
--- a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemsRepresentation.java
+++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/ItemsRepresentation.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0, which is available at
@@ -82,7 +82,7 @@
             },
             rel = "self"
     )
-    @XmlJavaTypeAdapter(Link.JaxbAdapter.class)
+    @XmlJavaTypeAdapter(JaxbAdapter.class)
     @XmlElement(name = "link")
     Link self;
 
@@ -111,7 +111,7 @@
             )})
     @XmlElement(name = "link")
     @XmlElementWrapper(name = "links")
-    @XmlJavaTypeAdapter(Link.JaxbAdapter.class)
+    @XmlJavaTypeAdapter(JaxbAdapter.class)
     List<Link> links;
 
     public ItemsRepresentation() {
diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/JaxbAdapter.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/JaxbAdapter.java
new file mode 100644
index 0000000..b6385dd
--- /dev/null
+++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/JaxbAdapter.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0, which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+
+package org.glassfish.jersey.examples.linking.representation;
+
+import jakarta.ws.rs.core.Link;
+import jakarta.xml.bind.annotation.adapters.XmlAdapter;
+
+import javax.xml.namespace.QName;
+import java.util.Map;
+
+/**
+ * An implementation of JAXB {@link XmlAdapter} that maps the JAX-RS
+ * {@link Link} type to a value that can be marshalled and unmarshalled by JAXB. The following example
+ * shows how to use this adapter on a JAXB bean class:
+ *
+ * <pre>
+ * &#64;XmlRootElement
+ * public class MyModel {
+ *
+ *   private Link link;
+ *
+ *   &#64;XmlElement(name="link")
+ *   &#64;XmlJavaTypeAdapter(JaxbAdapter.class)
+ *   public Link getLink() {
+ *     return link;
+ *   }
+ *   ...
+ * }
+ * </pre>
+ *
+ * <p>
+ *     Note that usage of this class requires the Jakarta XML Binding API and an implementation. The Jakarta RESTful Web
+ *     Services implementation is not required to provide these dependencies.
+ * </p>
+ * <p>
+ *     The class used to be a part Jakarta REST 3.1
+ * </p>
+ *
+ * @see JaxbLink
+ * @since 4.0
+ */
+public class JaxbAdapter extends XmlAdapter<JaxbLink, Link> {
+
+    /**
+     * Convert a {@link JaxbLink} into a {@link Link}.
+     *
+     * @param v instance of type {@link JaxbLink}.
+     * @return mapped instance of type {@link JaxbLink}
+     */
+    @Override
+    public Link unmarshal(final JaxbLink v) {
+        Link.Builder lb = Link.fromUri(v.getUri());
+        for (Map.Entry<QName, Object> e : v.getParams().entrySet()) {
+            lb.param(e.getKey().getLocalPart(), e.getValue().toString());
+        }
+        return lb.build();
+    }
+
+    /**
+     * Convert a {@link Link} into a {@link JaxbLink}.
+     *
+     * @param v instance of type {@link Link}.
+     * @return mapped instance of type {@link JaxbLink}.
+     */
+    @Override
+    public JaxbLink marshal(final Link v) {
+        JaxbLink jl = new JaxbLink(v.getUri());
+        for (Map.Entry<String, String> e : v.getParams().entrySet()) {
+            final String name = e.getKey();
+            jl.getParams().put(new QName("", name), e.getValue());
+        }
+        return jl;
+    }
+}
diff --git a/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/JaxbLink.java b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/JaxbLink.java
new file mode 100644
index 0000000..7653ffc
--- /dev/null
+++ b/examples/declarative-linking/src/main/java/org/glassfish/jersey/examples/linking/representation/JaxbLink.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.examples.linking.representation;
+
+import jakarta.xml.bind.annotation.XmlAnyAttribute;
+import jakarta.xml.bind.annotation.XmlAttribute;
+
+import javax.xml.namespace.QName;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * <p>
+ *     Value type for {@link jakarta.ws.rs.core.Link} that can be marshalled and
+ *     unmarshalled by JAXB.
+ * </p>
+ * <p>
+ *     Note that usage of this class requires the Jakarta XML Binding API and an implementation. The Jakarta RESTful Web
+ *     Services implementation is not required to provide these dependencies.
+ * </p>
+ * <p>
+ *     The class used to be a part Jakarta REST API 3.1.
+ * </p>
+ *
+ * @see JaxbAdapter
+ * @since 4.0
+ */
+public class JaxbLink {
+
+    private URI uri;
+    private Map<QName, Object> params;
+
+    /**
+     * Default constructor needed during unmarshalling.
+     */
+    public JaxbLink() {
+    }
+
+    /**
+     * Construct an instance from a URI and no parameters.
+     *
+     * @param uri underlying URI.
+     */
+    public JaxbLink(final URI uri) {
+        this.uri = uri;
+    }
+
+    /**
+     * Construct an instance from a URI and some parameters.
+     *
+     * @param uri underlying URI.
+     * @param params parameters of this link.
+     */
+    public JaxbLink(final URI uri, final Map<QName, Object> params) {
+        this.uri = uri;
+        this.params = params;
+    }
+
+    /**
+     * Get the underlying URI for this link.
+     *
+     * @return underlying URI.
+     */
+    @XmlAttribute(name = "href")
+    public URI getUri() {
+        return uri;
+    }
+
+    /**
+     * Get the parameter map for this link.
+     *
+     * @return parameter map.
+     */
+    @XmlAnyAttribute
+    public Map<QName, Object> getParams() {
+        if (params == null) {
+            params = new HashMap<QName, Object>();
+        }
+        return params;
+    }
+
+    /**
+     * Set the underlying URI for this link.
+     *
+     * This setter is needed for JAXB unmarshalling.
+     */
+    void setUri(final URI uri) {
+        this.uri = uri;
+    }
+
+    /**
+     * Set the parameter map for this link.
+     *
+     * This setter is needed for JAXB unmarshalling.
+     */
+    void setParams(final Map<QName, Object> params) {
+        this.params = params;
+    }
+
+    @Override
+    public boolean equals(final Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof JaxbLink)) {
+            return false;
+        }
+
+        JaxbLink jaxbLink = (JaxbLink) o;
+
+        if (uri != null ? !uri.equals(jaxbLink.uri) : jaxbLink.uri != null) {
+            return false;
+        }
+
+        if (params == jaxbLink.params) {
+            return true;
+        }
+        if (params == null) {
+            // if this.params is 'null', consider other.params equal to empty
+            return jaxbLink.params.isEmpty();
+        }
+        if (jaxbLink.params == null) {
+            // if other.params is 'null', consider this.params equal to empty
+            return params.isEmpty();
+        }
+
+        return params.equals(jaxbLink.params);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(uri, params);
+    }
+
+}
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/FieldProcessorTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/FieldProcessorTest.java
index bbac5b1..11458ee 100644
--- a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/FieldProcessorTest.java
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/FieldProcessorTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -147,6 +147,14 @@
             throw new UnsupportedOperationException("Not supported yet.");
         }
 
+        public String getMatchedResourceTemplate() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public List<String> getMatchedResourceTemplates() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
         @Override
         public List<Object> getMatchedResources() {
             Object dummyResource = new Object() {
diff --git a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/HeaderProcessorTest.java b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/HeaderProcessorTest.java
index 34e0ff1..535908f 100644
--- a/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/HeaderProcessorTest.java
+++ b/incubator/declarative-linking/src/test/java/org/glassfish/jersey/linking/HeaderProcessorTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -114,6 +114,14 @@
             throw new UnsupportedOperationException("Not supported yet.");
         }
 
+        public List<String> getMatchedResourceTemplates() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
+        public String getMatchedResourceTemplate() {
+            throw new UnsupportedOperationException("Not supported yet.");
+        }
+
         public List<Object> getMatchedResources() {
             Object dummyResource = new Object() {};
             return Collections.singletonList(dummyResource);
diff --git a/pom.xml b/pom.xml
index 4be2cde..9dcdb8c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2296,7 +2296,7 @@
         <jaxb.ri.version>4.0.4</jaxb.ri.version>
         <jaxrs.api.spec.version>3.1</jaxrs.api.spec.version>
         <jaxrs.api.impl.version>3.1.0</jaxrs.api.impl.version>
-        <jakarta.rest.osgi.version>jakarta.ws.rs;version="[3.1,5)",jakarta.ws.rs.client;version="[3.1,5)",jakarta.ws.rs.container;version="[3.1,5)",jakarta.ws.rs.core;version="[3.1,5)",jakarta.ws.rs.ext;version="[3.1,5)"</jakarta.rest.osgi.version>
+        <jakarta.rest.osgi.version>jakarta.ws.rs;version="[3.1,5)",jakarta.ws.rs.client;version="[3.1,5)",jakarta.ws.rs.container;version="[3.1,5)",jakarta.ws.rs.core;version="[3.1,5)",jakarta.ws.rs.ext;version="[3.1,5)",jakarta.ws.rs.sse;version="[3.1,5)"</jakarta.rest.osgi.version>
         <jetty.osgi.version>org.eclipse.jetty.*;version="[11,15)"</jetty.osgi.version>
         <jetty.version>12.0.7</jetty.version>
         <jetty9.version>9.4.53.v20231009</jetty9.version>
diff --git a/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/routing/GetMatchedResourceTemplateTest.java b/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/routing/GetMatchedResourceTemplateTest.java
new file mode 100644
index 0000000..6e467d7
--- /dev/null
+++ b/tests/e2e-server/src/test/java/org/glassfish/jersey/tests/e2e/server/routing/GetMatchedResourceTemplateTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.tests.e2e.server.routing;
+
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.GET;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.PUT;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import jakarta.ws.rs.core.UriInfo;
+import org.glassfish.jersey.server.internal.routing.UriRoutingContext;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class GetMatchedResourceTemplateTest extends JerseyTest {
+
+    @Path("/resource")
+    public static class UriInfoTestResource {
+        public static final String ONE_POST = "{name:[a-zA-Z][a-zA-Z_0-9]*}";
+        public static final String TWO_GET = "{Prefix}{p:/?}{id: ((\\d+)?)}/abc{p2:/?}{number: (([A-Za-z0-9]*)?)}";
+        public static final String TWO_POST = "{Prefix}{p:/?}{id: ((\\d+)?)}/abc/{yeah}";
+        public static final String THREE_SUB = "{x:[a-z]}";
+
+        public static class SubGet {
+            @PUT
+            public String get(@Context UriInfo uriInfo) {
+                return ((UriRoutingContext) uriInfo).getMatchedResourceTemplate();
+            }
+        }
+
+        @POST
+        @Path("one/" + ONE_POST)
+        public Response post(@Context UriInfo info) {
+            return Response.ok(((UriRoutingContext) info).getMatchedResourceTemplate()).build();
+        }
+
+        @GET
+        @Path("two/" + TWO_GET)
+        public Response get(@Context UriInfo info) {
+            return Response.ok(((UriRoutingContext) info).getMatchedResourceTemplate()).build();
+        }
+
+        @POST
+        @Path("two/" + TWO_POST)
+        public Response postTwo(@Context UriInfo info) {
+            return Response.ok(((UriRoutingContext) info).getMatchedResourceTemplate()).build();
+        }
+
+        @Path("three/" + THREE_SUB)
+        public SubGet doAnything4() {
+            return new SubGet();
+        }
+
+        @Path("four/" + THREE_SUB)
+        public UriInfoTestResource postOne() {
+            return new UriInfoTestResource();
+        }
+    }
+
+    @ApplicationPath("/app")
+    private static class App extends Application {
+        @Override
+        public Set<Class<?>> getClasses() {
+            return Collections.singleton(UriInfoTestResource.class);
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        return new App();
+    }
+
+    @Test
+    public void testPOSTone() {
+        try (Response r = target("app/resource/one/azazaz00").request().post(null)) {
+            Assertions.assertEquals(200, r.getStatus());
+            Assertions.assertEquals("/app/resource/one/" + UriInfoTestResource.ONE_POST, r.readEntity(String.class));
+        }
+    }
+
+    @Test
+    public void testTWOget() {
+        try (Response r = target("app/resource/two/P/abc/MyNumber").request().get()) {
+            Assertions.assertEquals(200, r.getStatus());
+            Assertions.assertEquals("/app/resource/two/" + UriInfoTestResource.TWO_GET, r.readEntity(String.class));
+        }
+    }
+
+    @Test
+    public void testTWOpost() {
+        try (Response r = target("app/resource/two/P/abc/MyNumber").request().post(null)) {
+            Assertions.assertEquals(200, r.getStatus());
+            Assertions.assertEquals("/app/resource/two/" + UriInfoTestResource.TWO_POST, r.readEntity(String.class));
+        }
+    }
+
+    @Test
+    public void testPUTthree2() {
+        try (Response r = target("app/resource/three/a").request().put(Entity.entity("", MediaType.WILDCARD_TYPE))) {
+            Assertions.assertEquals(200, r.getStatus());
+            Assertions.assertEquals("/app/resource/three/" + UriInfoTestResource.THREE_SUB, r.readEntity(String.class));
+        }
+    }
+}