Added UriInfo#getMatchedResourceTemplate method (#1236)

* Added UriInfo#getMatchedResourceTemplate method

Signed-off-by: jansupol <jan.supol@oracle.com>

* Reflected review comments
Added TCK tests

Signed-off-by: jansupol <jan.supol@oracle.com>

* Renamed the client class

Signed-off-by: jansupol <jan.supol@oracle.com>

* Added @Path to Sub-resource method

Signed-off-by: jansupol <jan.supol@oracle.com>

---------

Signed-off-by: jansupol <jan.supol@oracle.com>
diff --git a/jaxrs-api/src/main/java/jakarta/ws/rs/core/UriInfo.java b/jaxrs-api/src/main/java/jakarta/ws/rs/core/UriInfo.java
index 26a0a9f..2fad294 100644
--- a/jaxrs-api/src/main/java/jakarta/ws/rs/core/UriInfo.java
+++ b/jaxrs-api/src/main/java/jakarta/ws/rs/core/UriInfo.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2019 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
@@ -18,6 +18,8 @@
 
 import java.net.URI;
 import java.util.List;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.ApplicationPath;
 
 /**
  * An injectable interface that provides access to application and request URI information. Relative URIs are relative
@@ -231,6 +233,71 @@
     public List<String> getMatchedURIs();
 
     /**
+     * <p>
+     * Get a URI template that includes all {@link Path Paths} (including {@link ApplicationPath})
+     * matched by the current request's URI.
+     * </p>
+     * <p>
+     * Each {@link Path} value used to match a resource class, a sub-resource method or a sub-resource locator is concatenated
+     * into a single {@code String} value. The template does not include query parameters but does include matrix parameters
+     * if present in the request URI. The concatenation is ordered in the request URI matching order, with the
+     * {@link ApplicationPath} value first and current resource URI last. E.g. given the following resource classes:
+     * </p>
+     *
+     * <pre>
+     * &#064;Path("foo")
+     * public class FooResource {
+     *  &#064;GET
+     *  &#064;Path("{foo:[f-z][a-z]*}")
+     *  public String getFoo() {...}
+     *
+     *  &#064;Path("{bar:[b-e][a-z]*}")
+     *  public BarResource getBarResource() {...}
+     * }
+     *
+     * public class BarResource {
+     *  &#064;GET
+     *  &#064;Path("{id}{id:[0-9]}")
+     *  public String getBar() {...}
+     * }
+     * </pre>
+     *
+     * <p>
+     * The values returned by this method based on request uri and where the method is called from are:
+     * </p>
+     *
+     * <table border="1">
+     * <caption>Matched URIs from requests</caption>
+     * <tr>
+     * <th>Request</th>
+     * <th>Called from</th>
+     * <th>Value(s)</th>
+     * </tr>
+     * <tr>
+     * <td>GET /foo</td>
+     * <td>FooResource.getFoo</td>
+     * <td>/foo/{foo:[f-z][a-z]*}</td>
+     * </tr>
+     * <tr>
+     * <td>GET /foo/bar</td>
+     * <td>FooResource.getBarResource</td>
+     * <td>/foo/{bar:[b-e][a-z]*}</td>
+     * </tr>
+     * <tr>
+     * <td>GET /foo/bar/id0</td>
+     * <td>BarResource.getBar</td>
+     * <td>/foo/{bar:[b-e][a-z]*}/{id}{id:[0-9]}</td>
+     * </tr>
+     * </table>
+     *
+     * In case the method is invoked prior to the request matching (e.g. from a pre-matching filter), the method returns an
+     * empty string.
+     *
+     * @return A concatenated string of {@link Path} templates.
+     */
+    public String getMatchedResourceTemplate();
+
+    /**
      * Get a read-only list of URIs for matched resources.
      *
      * Each entry is a relative URI that matched a resource class, a sub-resource method or a sub-resource locator. Entries
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs40/ee/rs/core/uriinfo/TSAppConfig.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs40/ee/rs/core/uriinfo/TSAppConfig.java
new file mode 100644
index 0000000..afcfdda
--- /dev/null
+++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs40/ee/rs/core/uriinfo/TSAppConfig.java
@@ -0,0 +1,33 @@
+/*
+ * 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 ee.jakarta.tck.ws.rs.jaxrs40.ee.rs.core.uriinfo;
+
+import jakarta.ws.rs.ApplicationPath;
+import jakarta.ws.rs.core.Application;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@ApplicationPath("/app")
+public class TSAppConfig extends Application {
+
+  public Set<Class<?>> getClasses() {
+    Set<Class<?>> resources = new HashSet<Class<?>>();
+    resources.add(UriInfoTestResource.class);
+    return resources;
+  }
+}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs40/ee/rs/core/uriinfo/UriInfo40ClientIT.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs40/ee/rs/core/uriinfo/UriInfo40ClientIT.java
new file mode 100644
index 0000000..f6c20ad
--- /dev/null
+++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs40/ee/rs/core/uriinfo/UriInfo40ClientIT.java
@@ -0,0 +1,125 @@
+/*
+ * 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 ee.jakarta.tck.ws.rs.jaxrs40.ee.rs.core.uriinfo;
+
+import ee.jakarta.tck.ws.rs.common.JAXRSCommonClient;
+import ee.jakarta.tck.ws.rs.lib.util.TestUtil;
+import jakarta.ws.rs.core.UriInfo;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit5.ArquillianExtension;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+import java.io.IOException;
+
+/*
+ * @class.setup_props: webServerHost;
+ *                     webServerPort;
+ */
+@ExtendWith(ArquillianExtension.class)
+public class UriInfo40ClientIT extends JAXRSCommonClient {
+  protected static final String ROOT = "jaxrs40_ee_core_uriinfo_web";
+
+  protected static final String RESOURCE = "app/resource";
+
+  public UriInfo40ClientIT() {
+    setup();
+    setContextRoot("/" + ROOT + "/" + RESOURCE);
+  }
+
+
+  @BeforeEach
+  void logStartTest(TestInfo testInfo) {
+    TestUtil.logMsg("STARTING TEST : " + testInfo.getDisplayName());
+  }
+
+  @AfterEach
+  void logFinishTest(TestInfo testInfo) {
+    TestUtil.logMsg("FINISHED TEST : " + testInfo.getDisplayName());
+  }
+
+  @Deployment(testable = false)
+  public static WebArchive createDeployment() throws IOException{
+    WebArchive archive = ShrinkWrap.create(WebArchive.class, "jaxrs40_ee_core_uriinfo_web.war");
+    archive.addClasses(TSAppConfig.class, UriInfoTestResource.class);
+    return archive;
+  }
+
+  /* Run test */
+
+  /**
+   * @testName: getMatchedResourceTemplateOneTest
+   * 
+   * @assertion_ids: JAXRS:JAVADOC:97;
+   *
+   * @test_Strategy: Check the template containing {@link UriInfoTestResource#ONE_POST}
+   */
+  @Test
+  public void getMatchedResourceTemplateOneTest() throws Fault {
+    setProperty(Property.REQUEST, buildRequest(Request.POST, "one/azazaz00"));
+    setProperty(Property.SEARCH_STRING, "/app/resource/one/" + UriInfoTestResource.ONE_POST);
+    invoke();
+  }
+
+  /**
+   * @testName: getMatchedResourceTemplateTwoGetTest
+   *
+   * @assertion_ids: JAXRS:JAVADOC:97;
+   *
+   * @test_Strategy: Check the template containing {@link UriInfoTestResource#TWO_GET}
+   */
+  @Test
+  public void getMatchedResourceTemplateTwoGetTest() throws Fault {
+    setProperty(Property.REQUEST, buildRequest(Request.GET, "two/P/abc/MyNumber"));
+    setProperty(Property.SEARCH_STRING, "/app/resource/two/" + UriInfoTestResource.TWO_GET);
+    invoke();
+  }
+
+  /**
+   * @testName: getMatchedResourceTemplateTwoPostTest
+   *
+   * @assertion_ids: JAXRS:JAVADOC:97;
+   *
+   * @test_Strategy: Check the template containing {@link UriInfoTestResource#TWO_POST}
+   */
+  @Test
+  public void getMatchedResourceTemplateTwoPostTest() throws Fault {
+    setProperty(Property.REQUEST, buildRequest(Request.POST, "two/P/abc/MyNumber"));
+    setProperty(Property.SEARCH_STRING, "/app/resource/two/" + UriInfoTestResource.TWO_POST);
+    invoke();
+  }
+
+  /**
+   * @testName: getMatchedResourceTemplateSubTest
+   *
+   * @assertion_ids: JAXRS:JAVADOC:97;
+   *
+   * @test_Strategy: Check the template including subresource containing {@link UriInfoTestResource#THREE_SUB}
+   */
+  @Test
+  public void getMatchedResourceTemplateSubTest() throws Fault {
+    setProperty(Property.REQUEST, buildRequest(Request.PUT, "three/a/z"));
+    setProperty(Property.SEARCH_STRING,
+            "/app/resource/three/" + UriInfoTestResource.THREE_SUB + "/" + UriInfoTestResource.THREE_SUB);
+    invoke();
+  }
+}
diff --git a/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs40/ee/rs/core/uriinfo/UriInfoTestResource.java b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs40/ee/rs/core/uriinfo/UriInfoTestResource.java
new file mode 100644
index 0000000..f648482
--- /dev/null
+++ b/jaxrs-tck/src/main/java/ee/jakarta/tck/ws/rs/jaxrs40/ee/rs/core/uriinfo/UriInfoTestResource.java
@@ -0,0 +1,64 @@
+/*
+ * 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 ee.jakarta.tck.ws.rs.jaxrs40.ee.rs.core.uriinfo;
+
+import jakarta.ws.rs.GET;
+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;
+
+@Path("/resource")
+public 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
+        @Path(THREE_SUB)
+        public String get(@Context UriInfo uriInfo) {
+            return uriInfo.getMatchedResourceTemplate();
+        }
+    }
+
+    @POST
+    @Path("one/" + ONE_POST)
+    public Response post(@Context UriInfo info) {
+        return Response.ok(info.getMatchedResourceTemplate()).build();
+    }
+
+    @GET
+    @Path("two/" + TWO_GET)
+    public Response get(@Context UriInfo info) {
+        return Response.ok(info.getMatchedResourceTemplate()).build();
+    }
+
+    @POST
+    @Path("two/" + TWO_POST)
+    public Response postTwo(@Context UriInfo info) {
+        return Response.ok(info.getMatchedResourceTemplate()).build();
+    }
+
+    @Path("three/" + THREE_SUB)
+    public SubGet doAnything4() {
+        return new SubGet();
+    }
+}