You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2020/03/19 18:17:48 UTC

[juneau] branch master updated: JUNEAU-204

This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 1113cde  JUNEAU-204
1113cde is described below

commit 1113cded0c0b8c169ca83b606e47b7d2ffb9aa13
Author: JamesBognar <ja...@apache.org>
AuthorDate: Thu Mar 19 14:17:31 2020 -0400

    JUNEAU-204
    
    Add @RestMethod(paths) to allow matching multiple paths.
---
 juneau-doc/docs/ReleaseNotes/8.1.4.html            | 30 ++++++++++
 .../rest/annotation2/PathAnnotationTest.java       | 33 ++++++++++
 .../org/apache/juneau/rest/RestMethodContext.java  | 70 +++++++++++++++++++---
 .../apache/juneau/rest/annotation/RestMethod.java  | 24 ++++++++
 .../rest/annotation/RestMethodConfigApply.java     |  4 +-
 5 files changed, 151 insertions(+), 10 deletions(-)

diff --git a/juneau-doc/docs/ReleaseNotes/8.1.4.html b/juneau-doc/docs/ReleaseNotes/8.1.4.html
index a80fb9b..e8875f5 100644
--- a/juneau-doc/docs/ReleaseNotes/8.1.4.html
+++ b/juneau-doc/docs/ReleaseNotes/8.1.4.html
@@ -311,6 +311,36 @@
 		</ul>
 	<li>
 		New {@link oaj.http.annotation.Path#required() @Path(required)} annotation support.
+		<br>A path can be marked as not-required when the path variable is resolved by a parent resource like so:
+		<p class='bpcode w800'>
+	<ja>@Rest</ja>(path=<js>"/parent/{p1}"</js>,children=Child.<jk>class</jk>)
+	<jk>public class</jk> Parent {
+		...
+	}
+
+	<ja>@Rest</ja>(path="/child")
+	<jk>public class</jk> Child {
+
+		<ja>@RestMethod</ja>(path="/")
+		<jk>public</jk> String doGet(<ja>@Path</ja>(name=<js>"p1"</js>,required=<jk>false</jk>) String p1) {
+			<jc>// p1 will be null when accessed via "/child"</jc>
+			<jc>// p1 will be non-null when accessed via "/parent/p1/child".</jc>
+		}
+		...
+	}
+		</p>
+		<br>This allows the child resource to be mapped to multiple parents that may resolve various different path variables.
+	<li>
+		New {@link oajr.annotation.RestMethod#paths() @RestMethod(paths)} annotation that allows you to map multiple
+		paths to the same Java method.
+		<br>Example:
+		<p class='bpcode w800'>
+	<ja>@RestMethod</ja>(
+		name=<jsf>GET</jsf>,
+		paths={<js>"/"</js>,<js>"/{foo}"</js>}
+	)
+	<jk>public</jk> String doGet(<ja>@Path</ja>(name=<js>"foo"</js>,required=<jk>false</jk>) String foo) {...}
+		</p>
 </ul>
 
 <h5 class='topic w800'>juneau-rest-server-springboot</h5>
diff --git a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathAnnotationTest.java b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathAnnotationTest.java
index 81ab56d..efbc375 100644
--- a/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathAnnotationTest.java
+++ b/juneau-rest/juneau-rest-server-utest/src/test/java/org/apache/juneau/rest/annotation2/PathAnnotationTest.java
@@ -1135,4 +1135,37 @@ public class PathAnnotationTest {
 		u2.get("/foo/xxx").execute().assertStatus(200).assertBody("nil,xxx");
 	}
 
+	//=================================================================================================================
+	// Multiple paths
+	//=================================================================================================================
+
+	@Rest(path="/v1/{v1}",children=V2.class)
+	public static class V1 {
+	}
+	@Rest(path="/v2")
+	public static class V2 {
+		@RestMethod(paths={"/","/{foo}"})
+		public String doGet(@Path(name="v1",required=false) String v1, @Path(name="foo",required=false) String foo) {
+			return "1," + (v1 == null ? "nil" : v1) + "," + (foo == null ? "nil" : foo);
+		}
+		@RestMethod(paths={"/foo","/foo/{foo}"})
+		public String doGet2(@Path(name="v1",required=false) String v1, @Path(name="foo",required=false) String foo) {
+			return "2," + (v1 == null ? "nil" : v1) + "," + (foo == null ? "nil" : foo);
+		}
+	}
+
+	static MockRest v1 = MockRest.build(V1.class, null);
+	static MockRest v2 = MockRest.build(V2.class, null);
+
+	@Test
+	public void v01_multiplePaths() throws Exception {
+		v1.get("/v1/v1foo/v2").execute().assertStatus(200).assertBody("1,v1foo,nil");
+		v1.get("/v1/v1foo/v2/v2foo").execute().assertStatus(200).assertBody("1,v1foo,v2foo");
+		v1.get("/v1/v1foo/v2/foo").execute().assertStatus(200).assertBody("2,v1foo,nil");
+		v1.get("/v1/v1foo/v2/foo/v2foo").execute().assertStatus(200).assertBody("2,v1foo,v2foo");
+		v2.get("/v2").execute().assertStatus(200).assertBody("1,nil,nil");
+		v2.get("/v2/v2foo").execute().assertStatus(200).assertBody("1,nil,v2foo");
+		v2.get("/v2/foo").execute().assertStatus(200).assertBody("2,nil,nil");
+		v2.get("/v2/foo/v2foo").execute().assertStatus(200).assertBody("2,nil,v2foo");
+	}
 }
\ No newline at end of file
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
index 669d6d3..879e0b4 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestMethodContext.java
@@ -431,10 +431,46 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 	 * 		Slashes are trimmed from the path ends.
 	 * 		<br>As a convention, you may want to start your path with <js>'/'</js> simple because it make it easier to read.
 	 * </ul>
+	 * @deprecated Use {@link #RESTMETHOD_paths}
 	 */
+	@Deprecated
 	public static final String RESTMETHOD_path = PREFIX + ".path.s";
 
 	/**
+	 * Configuration property:  Resource method paths.
+	 *
+	 * <h5 class='section'>Property:</h5>
+	 * <ul class='spaced-list'>
+	 * 	<li><b>ID:</b>  {@link org.apache.juneau.rest.RestMethodContext#RESTMETHOD_paths RESTMETHOD_paths}
+	 * 	<li><b>Name:</b>  <js>"RestMethodContext.path.ls"</js>
+	 * 	<li><b>Data type:</b>  <c>String[]</c>
+	 * 	<li><b>System property:</b>  <c>RestMethodContext.paths</c>
+	 * 	<li><b>Environment variable:</b>  <c>RESTMETHODCONTEXT_PATHS</c>
+	 * 	<li><b>Default:</b>  <jk>null</jk>
+	 * 	<li><b>Session property:</b>  <jk>false</jk>
+	 * 	<li><b>Annotations:</b>
+	 * 		<ul>
+	 * 			<li class='ja'>{@link org.apache.juneau.rest.annotation.RestMethod#path()}
+	 * 			<li class='ja'>{@link org.apache.juneau.rest.annotation.RestMethod#paths()}
+	 * 		</ul>
+	 * </ul>
+	 *
+	 * <h5 class='section'>Description:</h5>
+	 * <p>
+	 * Identifies the URL subpath relative to the servlet class.
+	 *
+	 * <p>
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		This method is only applicable for Java methods.
+	 * 	<li>
+	 * 		Slashes are trimmed from the path ends.
+	 * 		<br>As a convention, you may want to start your path with <js>'/'</js> simple because it make it easier to read.
+	 * </ul>
+	 */
+	public static final String RESTMETHOD_paths = PREFIX + ".paths.ls";
+
+	/**
 	 * Configuration property:  Priority.
 	 *
 	 * <h5 class='section'>Property:</h5>
@@ -556,7 +592,7 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 	//-------------------------------------------------------------------------------------------------------------------
 
 	private final String httpMethod;
-	private final UrlPathPattern pathPattern;
+	private final UrlPathPattern[] pathPatterns;
 	final RestMethodParam[] methodParams;
 	private final RestGuard[] guards;
 	private final RestMatcher[] optionalMatchers;
@@ -656,9 +692,15 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 
 		this.responseMeta = ResponseBeanMeta.create(mi, ps);
 
-		this.pathPattern = new UrlPathPattern(getProperty(RESTMETHOD_path, String.class, HttpUtils.detectHttpPath(method, true)));
+		List<UrlPathPattern> pathPatterns = new ArrayList<>();
+		for (String p : getArrayProperty(RESTMETHOD_paths, String.class))
+			pathPatterns.add(new UrlPathPattern(p));
+		if (pathPatterns.isEmpty())
+			pathPatterns.add(new UrlPathPattern(HttpUtils.detectHttpPath(method, true)));
+
+		this.pathPatterns = pathPatterns.toArray(new UrlPathPattern[pathPatterns.size()]);
 
-		this.methodParams = context.findParams(mi, false, pathPattern);
+		this.methodParams = context.findParams(mi, false, this.pathPatterns[this.pathPatterns.length-1]);
 
 		this.converters = getInstanceArrayProperty(REST_converters, RestConverter.class, new RestConverter[0], rr, r, this);
 
@@ -846,7 +888,7 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 	 * Returns the path pattern for this method.
 	 */
 	String getPathPattern() {
-		return pathPattern.toString();
+		return pathPatterns[0].toString();
 	}
 
 	/**
@@ -862,7 +904,10 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 	}
 
 	boolean matches(UrlPathInfo pathInfo) {
-		return pathPattern.match(pathInfo) != null;
+		for (UrlPathPattern p : pathPatterns)
+			if (p.match(pathInfo) != null)
+				return true;
+		return false;
 	}
 
 	/**
@@ -873,7 +918,12 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 	 */
 	int invoke(RestCall call) throws Throwable {
 
-		UrlPathPatternMatch pm = pathPattern.match(call.getUrlPathInfo());
+		UrlPathPatternMatch pm = null;
+
+		for (UrlPathPattern pp : pathPatterns)
+			if (pm == null)
+				pm = pp.match(call.getUrlPathInfo());
+
 		if (pm == null)
 			return SC_NOT_FOUND;
 
@@ -1021,9 +1071,11 @@ public class RestMethodContext extends BeanContext implements Comparable<RestMet
 		if (c != 0)
 			return c;
 
-		c = pathPattern.compareTo(o.pathPattern);
-		if (c != 0)
-			return c;
+		for (int i = 0; i < Math.min(pathPatterns.length, o.pathPatterns.length); i++) {
+			c = pathPatterns[i].compareTo(o.pathPatterns[i]);
+			if (c != 0)
+				return c;
+		}
 
 		c = compare(o.hierarchyDepth, hierarchyDepth);
 		if (c != 0)
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
index 5f9b0f8..8bd8727 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethod.java
@@ -22,6 +22,7 @@ import org.apache.juneau.annotation.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.remote.*;
 import org.apache.juneau.html.annotation.*;
+import org.apache.juneau.http.annotation.*;
 
 /**
  * Identifies a REST Java method on a {@link RestServlet} implementation class.
@@ -586,6 +587,29 @@ public @interface RestMethod {
 	String path() default "";
 
 	/**
+	 * Same as {@link #path()} but allows you to specify multiple path patterns for the same method.
+	 *
+	 * <p>
+	 * The path can contain variables that get resolved to {@link org.apache.juneau.http.annotation.Path @Path} parameters.
+	 * <br>When variables are not defined on all paths, the {@link Path#required @Path(required)} annotation can be set
+	 * to <jk>false</jk> to make it optional.
+	 *
+	 * <h5 class='figure'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<ja>@RestMethod</ja>(
+	 * 		name=<jsf>GET</jsf>,
+	 * 		paths={<js>"/"</js>,<js>"/{foo}"</js>}
+	 * 	)
+	 * 	<jk>public</jk> String doGet(<ja>@Path</ja>(name=<js>"foo"</js>,required=<jk>false</jk>) String foo) {...}
+	 * </p>
+	 *
+	 * <ul class='seealso'>
+	 * 	<li class='ja'>{@link org.apache.juneau.http.annotation.Path}
+	 * </ul>
+	 */
+	String[] paths() default {};
+
+	/**
 	 * Sets the POJO swaps for the serializers and parsers defined on this method.
 	 *
 	 * <div class='warn'>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethodConfigApply.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethodConfigApply.java
index 9e5dad6..7e84695 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethodConfigApply.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestMethodConfigApply.java
@@ -159,7 +159,9 @@ public class RestMethodConfigApply extends ConfigApply<RestMethod> {
 			psb.set(REST_maxInput, string(a.maxInput()));
 
 		if (! a.path().isEmpty())
-			psb.set(RESTMETHOD_path, string(a.path()));
+			psb.addTo(RESTMETHOD_paths, string(a.path()));
+		for (String p : a.paths())
+			psb.addTo(RESTMETHOD_paths, string(p));
 
 		if (! a.rolesDeclared().isEmpty())
 			psb.addTo(REST_rolesDeclared, strings(a.rolesDeclared()));