Skip to content

Commit 6c8feaa

Browse files
committed
add .excludes pattern to routes enhancement fixes #175
1 parent 475b825 commit 6c8feaa

File tree

6 files changed

+208
-26
lines changed

6 files changed

+208
-26
lines changed
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package org.jooby;
2+
3+
import org.jooby.mvc.GET;
4+
import org.jooby.mvc.Path;
5+
import org.jooby.test.ServerFeature;
6+
import org.junit.Test;
7+
8+
public class RouteExcludesFeature extends ServerFeature {
9+
10+
@Path(value = "/parent/**", excludes = "/parent/logout")
11+
public static class ResourceWithParentExcludes {
12+
13+
@GET
14+
public String handle(final Request req) {
15+
return req.path();
16+
}
17+
}
18+
19+
public static class ResourceWithMethodExcludes {
20+
21+
@GET
22+
@Path(value = "/m/**", excludes = "/m/logout")
23+
public String handle(final Request req) {
24+
return req.path();
25+
}
26+
}
27+
28+
@Path(value = "/merge/**", excludes = "/merge/logout")
29+
public static class ResourceWithMergeExcludes {
30+
31+
@GET
32+
@Path(value = "/", excludes = "/merge/login")
33+
public String handle(final Request req) {
34+
return req.path();
35+
}
36+
}
37+
38+
{
39+
40+
use(ResourceWithParentExcludes.class);
41+
42+
use(ResourceWithMethodExcludes.class);
43+
44+
use(ResourceWithMergeExcludes.class);
45+
46+
use("/path/**", req -> req.path())
47+
.excludes("/path/logout");
48+
49+
}
50+
51+
@Test
52+
public void excludes() throws Exception {
53+
request()
54+
.get("/path")
55+
.expect("/path");
56+
57+
request()
58+
.get("/path/x")
59+
.expect("/path/x");
60+
61+
request()
62+
.get("/logout")
63+
.expect(404);
64+
}
65+
66+
@Test
67+
public void parentMvc() throws Exception {
68+
request()
69+
.get("/parent")
70+
.expect("/parent");
71+
72+
request()
73+
.get("/parent/x")
74+
.expect("/parent/x");
75+
76+
request()
77+
.get("/parent/logout")
78+
.expect(404);
79+
}
80+
81+
@Test
82+
public void methodMvc() throws Exception {
83+
request()
84+
.get("/m")
85+
.expect("/m");
86+
87+
request()
88+
.get("/m/x")
89+
.expect("/m/x");
90+
91+
request()
92+
.get("/m/logout")
93+
.expect(404);
94+
}
95+
96+
@Test
97+
public void mergeMvc() throws Exception {
98+
request()
99+
.get("/merge")
100+
.expect("/merge");
101+
102+
request()
103+
.get("/merge/x")
104+
.expect("/merge/x");
105+
106+
request()
107+
.get("/merge/logout")
108+
.expect(404);
109+
110+
request()
111+
.get("/merge/login")
112+
.expect(404);
113+
}
114+
115+
}

jooby/src/main/java/org/jooby/Jooby.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -762,6 +762,18 @@ public Route.Definition use(final String path,
762762
return appendDefinition(new Route.Definition("*", path, handler));
763763
}
764764

765+
/**
766+
* Append a new route handler that matches any method under the given path.
767+
*
768+
* @param path A path pattern.
769+
* @param handler A handler to execute.
770+
* @return A new route definition.
771+
*/
772+
public Route.Definition use(final String path,
773+
final Route.OneArgHandler handler) {
774+
return appendDefinition(new Route.Definition("*", path, handler));
775+
}
776+
765777
/**
766778
* Append a route that supports HTTP GET method:
767779
*

jooby/src/main/java/org/jooby/Route.java

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.jooby.internal.RouteImpl;
3434
import org.jooby.internal.RouteMatcher;
3535
import org.jooby.internal.RoutePattern;
36+
import org.jooby.util.Collectors;
3637

3738
import com.google.common.base.Strings;
3839
import com.google.common.collect.ImmutableList;
@@ -165,32 +166,19 @@
165166
* });
166167
* </pre>
167168
*
168-
* <h2>External route</h2>
169+
* <h2>Mvc Route</h2>
169170
* <p>
170-
* An external route can be defined by using a {@link Class route class}, like:
171+
* A Mvc Route use annotations to define routes:
171172
* </p>
172173
*
173174
* <pre>
174-
* get("/", handler(ExternalRoute.class)); //or
175-
*
176-
* ...
177-
* // ExternalRoute.java
178-
* public class ExternalRoute implements Route.Handler {
179-
* public void handle(Request req, Response rsp) throws Exception {
180-
* rsp.send("Hello Jooby");
181-
* }
182-
* }
175+
* {
176+
* use(MyRoute.class);
177+
* }
183178
* </pre>
184179
*
185-
* <h2>Mvc Route</h2>
186-
* <p>
187-
* A Mvc Route use annotations to define routes:
188-
* </p>
189-
*
180+
* MyRoute.java:
190181
* <pre>
191-
* route(MyRoute.class);
192-
* ...
193-
* // MyRoute.java
194182
* {@literal @}Path("/")
195183
* public class MyRoute {
196184
*
@@ -759,6 +747,8 @@ class Definition {
759747
*/
760748
private String pattern;
761749

750+
private List<RoutePattern> excludes = Collections.emptyList();
751+
762752
/**
763753
* Creates a new route definition.
764754
*
@@ -871,7 +861,11 @@ public List<String> vars() {
871861
public Optional<Route> matches(final String verb,
872862
final String path, final MediaType contentType,
873863
final List<MediaType> accept) {
874-
RouteMatcher matcher = compiledPattern.matcher(verb.toUpperCase() + path);
864+
String fpath = verb.toUpperCase() + path;
865+
if (excludes(fpath)) {
866+
return Optional.empty();
867+
}
868+
RouteMatcher matcher = compiledPattern.matcher(fpath);
875869
if (matcher.matches()) {
876870
List<MediaType> result = MediaType.matcher(accept).filter(this.produces);
877871
if (result.size() > 0 && canConsume(contentType)) {
@@ -1032,6 +1026,36 @@ public Definition produces(final List<MediaType> produces) {
10321026
return this;
10331027
}
10341028

1029+
/**
1030+
* Excludes one or more path pattern from this route, useful for filter:
1031+
*
1032+
* <pre>
1033+
* {
1034+
* use("*", req {@literal ->} {
1035+
* ...
1036+
* }).excludes("/logout");
1037+
* }
1038+
* </pre>
1039+
*
1040+
* @param excludes A path pattern.
1041+
* @return This route definition.
1042+
*/
1043+
public Definition excludes(final String... excludes) {
1044+
this.excludes = Arrays.asList(excludes).stream()
1045+
.map(it -> new RoutePattern(method, it))
1046+
.collect(Collectors.toList());
1047+
return this;
1048+
}
1049+
1050+
private boolean excludes(final String path) {
1051+
for (RoutePattern pattern: excludes) {
1052+
if (pattern.matcher(path).matches()) {
1053+
return true;
1054+
}
1055+
}
1056+
return false;
1057+
}
1058+
10351059
/**
10361060
* @return All the types this route can consumes.
10371061
*/
@@ -1051,7 +1075,8 @@ public String toString() {
10511075
StringBuilder buffer = new StringBuilder();
10521076
buffer.append(method()).append(" ").append(pattern()).append("\n");
10531077
buffer.append(" name: ").append(name()).append("\n");
1054-
buffer.append(" consume: ").append(consumes()).append("\n");
1078+
buffer.append(" excludes: ").append(excludes).append("\n");
1079+
buffer.append(" consumes: ").append(consumes()).append("\n");
10551080
buffer.append(" produces: ").append(produces()).append("\n");
10561081
return buffer.toString();
10571082
}

jooby/src/main/java/org/jooby/internal/mvc/MvcRoutes.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656

5757
public class MvcRoutes {
5858

59-
private static final String[] NO_PATHS = new String[0];
59+
private static final String[] EMPTY = new String[0];
6060

6161
@SuppressWarnings("unchecked")
6262
private static final Set<Class<? extends Annotation>> VERBS = ImmutableSet.of(GET.class,
@@ -71,6 +71,7 @@ public static List<Route.Definition> routes(final Env env, final RouteMetadata c
7171
new RequestParamProviderImpl(new RequestParamNameProviderImpl(classInfo));
7272

7373
String[] rootPaths = path(routeClass);
74+
String[] rootExcludes = excludes(routeClass, EMPTY);
7475

7576
Map<Method, List<Class<?>>> methods = new HashMap<>();
7677
for (Method method : routeClass.getDeclaredMethods()) {
@@ -117,10 +118,13 @@ public static List<Route.Definition> routes(final Env env, final RouteMetadata c
117118
for (Class<?> verb : verbs) {
118119
String name = routeClass.getSimpleName() + "." + method.getName();
119120

121+
String[] excludes = excludes(method, rootExcludes);
122+
120123
Definition definition = new Route.Definition(
121124
verb.getSimpleName(), path, new MvcHandler(method, paramProvider))
122125
.produces(produces)
123126
.consumes(consumes)
127+
.excludes(excludes)
124128
.name(name);
125129

126130
definitions.add(definition);
@@ -168,11 +172,31 @@ private static List<MediaType> consumes(final Method method) {
168172
private static String[] path(final AnnotatedElement owner) {
169173
Path annotation = owner.getAnnotation(Path.class);
170174
if (annotation == null) {
171-
return NO_PATHS;
175+
return EMPTY;
172176
}
173177
return annotation.value();
174178
}
175179

180+
private static String[] excludes(final AnnotatedElement owner, final String[] parent) {
181+
Path annotation = owner.getAnnotation(Path.class);
182+
if (annotation == null) {
183+
return parent;
184+
}
185+
String[] excludes = annotation.excludes();
186+
if (excludes.length == 0) {
187+
return parent;
188+
}
189+
if (parent.length == 0) {
190+
return excludes;
191+
}
192+
// join everything
193+
int size = parent.length + excludes.length;
194+
String[] result = new String[size];
195+
System.arraycopy(parent, 0, result, 0, parent.length);
196+
System.arraycopy(excludes, 0, result, parent.length, excludes.length);
197+
return result;
198+
}
199+
176200
private static String[] expandPaths(final String[] root, final Method m) {
177201
String[] path = path(m);
178202
if (root.length == 0) {

jooby/src/main/java/org/jooby/mvc/Path.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,9 @@
7878
* @return Route path pattern.
7979
*/
8080
String[] value();
81+
82+
/**
83+
* @return Pattern to excludes/ignore. Useful for filters.
84+
*/
85+
String[] excludes() default {};
8186
}

jooby/src/test/java/org/jooby/RouteDefinitionTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,12 @@ public void toStr() throws Exception {
114114
})
115115
.run(unit -> {
116116
Definition def = new Route.Definition("GET", "/", (req, rsp, chain) -> {
117-
});
117+
}).excludes("/**/logout");
118118

119119
assertEquals("GET /\n" +
120120
" name: anonymous\n" +
121-
" consume: [*/*]\n" +
121+
" excludes: [/**/logout]\n" +
122+
" consumes: [*/*]\n" +
122123
" produces: [*/*]\n", def.toString());
123124
});
124125
}
@@ -159,7 +160,7 @@ public void consumesMany() throws Exception {
159160
.isPresent());
160161
assertEquals(false,
161162
def.matches("GET", "/", MediaType.json, Arrays.asList(MediaType.html))
162-
.isPresent());
163+
.isPresent());
163164
}
164165

165166
@Test(expected = IllegalArgumentException.class)

0 commit comments

Comments
 (0)