Skip to content

Commit 69bc198

Browse files
vaadin-botDenis
andauthored
fix: consider first added route as main route and always return it first (#10556) (#10568)
fixes #10528 Co-authored-by: Denis <denis@vaadin.com>
1 parent 804b06d commit 69bc198

2 files changed

Lines changed: 151 additions & 22 deletions

File tree

flow-server/src/main/java/com/vaadin/flow/router/internal/RouteSegment.java

Lines changed: 104 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.util.LinkedHashMap;
2424
import java.util.List;
2525
import java.util.Map;
26+
import java.util.Map.Entry;
2627
import java.util.Objects;
2728
import java.util.Optional;
2829
import java.util.Set;
@@ -31,6 +32,8 @@
3132
import java.util.regex.Pattern;
3233

3334
import com.vaadin.flow.component.Component;
35+
import com.vaadin.flow.router.Route;
36+
import com.vaadin.flow.router.RouteAlias;
3437
import com.vaadin.flow.router.RouteParameters;
3538
import com.vaadin.flow.server.AmbiguousRouteConfigurationException;
3639

@@ -101,11 +104,18 @@ final class RouteSegment implements Serializable {
101104
*/
102105
private Map<String, RouteSegment> allSegments;
103106

104-
private RouteSegment() {
105-
}
107+
private final boolean isRoot;
108+
109+
/**
110+
* Main route segment should be returned first in the {@link #getRoutes()}
111+
* method to support {@link Route}/{@link RouteAlias} annotation semantic :
112+
* {@link Route} should take precedence over {@link RouteAlias}.
113+
*/
114+
private boolean isMainRouteSegment;
106115

107-
private RouteSegment(String segmentTemplate) {
116+
private RouteSegment(String segmentTemplate, boolean isRoot) {
108117
this.template = segmentTemplate;
118+
this.isRoot = isRoot;
109119

110120
if (RouteFormat.isParameter(segmentTemplate)) {
111121
info = new RouteFormat.ParameterInfo(segmentTemplate);
@@ -124,6 +134,8 @@ public RouteSegment(RouteSegment original) {
124134
this.info = original.info;
125135
this.pattern = original.pattern;
126136
this.target = original.target;
137+
this.isRoot = original.isRoot;
138+
this.isMainRouteSegment = original.isMainRouteSegment;
127139

128140
original.getStaticSegments().entrySet()
129141
.forEach(e -> this.addSegment(new RouteSegment(e.getValue()),
@@ -146,7 +158,7 @@ public RouteSegment(RouteSegment original) {
146158
* Create a new root segment instance.
147159
*/
148160
static RouteSegment createRoot() {
149-
return new RouteSegment("");
161+
return new RouteSegment("", true);
150162
}
151163

152164
String getName() {
@@ -203,24 +215,38 @@ boolean isEligible(String value) {
203215
* @return a {@link Map} containing all templates and their specific
204216
* targets.
205217
*/
206-
Map<String, RouteTarget> getRoutes() {
207-
208-
Map<String, RouteTarget> result = new LinkedHashMap<>();
218+
LinkedHashMap<String, RouteTarget> getRoutes() {
219+
220+
Map<String, RouteSegment> leafSegments = getLeafStaticSegments();
221+
222+
String mainRoutePath = null;
223+
RouteTarget mainRouteTarget = null;
224+
225+
/*
226+
* Find the first main route segment in the static segments which should
227+
* go first to be able to support Route/RouteAlias semantic. Main route
228+
* doesn't have to exist though: it may be removed at any point. In this
229+
* case the order doesn't matter since the RouteAlias ordering is not
230+
* defined.
231+
*/
232+
for (Entry<String, RouteSegment> entry : leafSegments.entrySet()) {
233+
if (entry.getValue().isMainRouteSegment) {
234+
mainRoutePath = entry.getKey();
235+
mainRouteTarget = entry.getValue().target;
236+
break;
237+
}
238+
}
209239

210-
if (target != null) {
211-
result.put("", target);
240+
// If there is the main route : add it as first
241+
LinkedHashMap<String, RouteTarget> result = new LinkedHashMap<>();
242+
if (mainRoutePath != null) {
243+
result.put(mainRoutePath, mainRouteTarget);
212244
}
213245

214-
collectRoutes(result, getStaticSegments());
215-
collectRoutes(result, getParameterSegments());
216-
collectRoutes(result, getOptionalSegments());
217-
collectRoutes(result, getVarargsSegments());
246+
getLeafSegments()
247+
.forEach((path, segment) -> result.put(path, segment.target));
218248

219-
if (getTemplate().isEmpty()) {
220-
return Collections.unmodifiableMap(result);
221-
} else {
222-
return result;
223-
}
249+
return result;
224250
}
225251

226252
void removeSubRoute(String template) {
@@ -292,13 +318,53 @@ String formatTemplate(String template,
292318
}
293319
}
294320

295-
private void collectRoutes(Map<String, RouteTarget> result,
321+
private LinkedHashMap<String, RouteSegment> getLeafSegments() {
322+
LinkedHashMap<String, RouteSegment> result = new LinkedHashMap<>();
323+
324+
if (target != null) {
325+
result.put("", this);
326+
}
327+
328+
collectLeafSegments(result, getStaticSegments());
329+
collectLeafSegments(result, getParameterSegments());
330+
collectLeafSegments(result, getOptionalSegments());
331+
collectLeafSegments(result, getVarargsSegments());
332+
333+
return result;
334+
}
335+
336+
private Map<String, RouteSegment> getLeafStaticSegments() {
337+
Map<String, RouteSegment> result = new HashMap<>();
338+
339+
if (target != null) {
340+
result.put("", this);
341+
}
342+
343+
for (Map.Entry<String, RouteSegment> segmentEntry : getStaticSegments()
344+
.entrySet()) {
345+
RouteSegment segment = segmentEntry.getValue();
346+
347+
for (Map.Entry<String, RouteSegment> targetEntry : segment
348+
.getLeafStaticSegments().entrySet()) {
349+
350+
final String key = targetEntry.getKey();
351+
result.put(
352+
segmentEntry.getKey()
353+
+ (key.isEmpty() ? "" : ("/" + key)),
354+
targetEntry.getValue());
355+
}
356+
}
357+
return result;
358+
}
359+
360+
private void collectLeafSegments(Map<String, RouteSegment> result,
296361
Map<String, RouteSegment> children) {
297362
for (Map.Entry<String, RouteSegment> segmentEntry : children
298363
.entrySet()) {
364+
RouteSegment segment = segmentEntry.getValue();
299365

300-
for (Map.Entry<String, RouteTarget> targetEntry : segmentEntry
301-
.getValue().getRoutes().entrySet()) {
366+
for (Map.Entry<String, RouteSegment> targetEntry : segment
367+
.getLeafSegments().entrySet()) {
302368

303369
final String key = targetEntry.getKey();
304370
result.put(
@@ -309,6 +375,16 @@ private void collectRoutes(Map<String, RouteTarget> result,
309375
}
310376
}
311377

378+
private RouteSegment getFirstLeafSegment() {
379+
if (target != null) {
380+
return this;
381+
}
382+
Map<String, RouteSegment> segments = getAllSegments();
383+
assert !segments.isEmpty();
384+
RouteSegment first = segments.values().iterator().next();
385+
return first.getFirstLeafSegment();
386+
}
387+
312388
private void removeSubRoute(List<String> segmentPatterns) {
313389
RouteSegment routeSegment;
314390
String segmentPattern = null;
@@ -341,6 +417,7 @@ private void removeSubRoute(List<String> segmentPatterns) {
341417
}
342418

343419
private void addSubRoute(List<String> segmentPatterns, RouteTarget target) {
420+
boolean isMainRoute = isEmpty() && isRoot;
344421

345422
RouteSegment routeSegment;
346423
String segmentPattern = null;
@@ -379,6 +456,11 @@ private void addSubRoute(List<String> segmentPatterns, RouteTarget target) {
379456
}
380457

381458
routeSegment.setRouteTarget(segmentPatterns, target);
459+
460+
if (isMainRoute) {
461+
RouteSegment firstSegment = getFirstLeafSegment();
462+
firstSegment.isMainRouteSegment = true;
463+
}
382464
}
383465

384466
private void setRouteTarget(List<String> segmentPatterns,
@@ -730,7 +812,7 @@ boolean isEmpty() {
730812

731813
private RouteSegment addSegment(String segmentTemplate,
732814
Map<String, RouteSegment> children) {
733-
RouteSegment routeSegment = new RouteSegment(segmentTemplate);
815+
RouteSegment routeSegment = new RouteSegment(segmentTemplate, false);
734816
addSegment(routeSegment, children);
735817
return routeSegment;
736818
}

flow-server/src/test/java/com/vaadin/flow/server/SessionRouteRegistryTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.vaadin.flow.server;
22

33
import javax.servlet.ServletContext;
4+
45
import java.io.ByteArrayInputStream;
56
import java.io.ByteArrayOutputStream;
67
import java.io.ObjectInputStream;
@@ -40,7 +41,9 @@
4041
import com.vaadin.flow.router.Route;
4142
import com.vaadin.flow.router.RouteAlias;
4243
import com.vaadin.flow.router.RouteBaseData;
44+
import com.vaadin.flow.router.RouteConfiguration;
4345
import com.vaadin.flow.router.RouteData;
46+
import com.vaadin.flow.router.RouteParameters;
4447
import com.vaadin.flow.router.RouterLayout;
4548
import com.vaadin.flow.router.RoutesChangedEvent;
4649
import com.vaadin.flow.router.internal.HasUrlParameterFormat;
@@ -988,6 +991,36 @@ public void serialize_deserialize_parentRegistryIsANewOne()
988991

989992
}
990993

994+
@Test
995+
public void getTargetUrl_annotatedRoute_rootIsAlias_mainRouteIsNotRoot_mainRouteIsReturned() {
996+
SessionRouteRegistry registry = getRegistry(session);
997+
RouteConfiguration configuration = RouteConfiguration
998+
.forRegistry(registry);
999+
1000+
configuration.setAnnotatedRoute(RouteWithRootAlias.class);
1001+
1002+
Optional<String> url = registry.getTargetUrl(RouteWithRootAlias.class,
1003+
RouteParameters.empty());
1004+
1005+
Assert.assertTrue(url.isPresent());
1006+
Assert.assertEquals("foo", url.get());
1007+
}
1008+
1009+
@Test
1010+
public void getTargetUrl_annotatedRoute_rootIsAlias_mainRouteIsParamerterized_routeAliasIsReturned() {
1011+
SessionRouteRegistry registry = getRegistry(session);
1012+
RouteConfiguration configuration = RouteConfiguration
1013+
.forRegistry(registry);
1014+
1015+
configuration.setAnnotatedRoute(ParameterizedRouteWithRootAlias.class);
1016+
1017+
Optional<String> url = registry.getTargetUrl(
1018+
ParameterizedRouteWithRootAlias.class, RouteParameters.empty());
1019+
1020+
Assert.assertTrue(url.isPresent());
1021+
Assert.assertEquals("", url.get());
1022+
}
1023+
9911024
private <T> T serializeAndDeserialize(T instance) throws Throwable {
9921025
ByteArrayOutputStream bs = new ByteArrayOutputStream();
9931026
ObjectOutputStream out = new ObjectOutputStream(bs);
@@ -1053,6 +1086,20 @@ public int setErrorParameter(BeforeEnterEvent event,
10531086
}
10541087
}
10551088

1089+
@Tag("div")
1090+
@Route("foo")
1091+
@RouteAlias("")
1092+
private static class RouteWithRootAlias extends Component {
1093+
1094+
}
1095+
1096+
@Tag("div")
1097+
@Route(":foo")
1098+
@RouteAlias("")
1099+
private static class ParameterizedRouteWithRootAlias extends Component {
1100+
1101+
}
1102+
10561103
/**
10571104
* Extending class to let us mock the getRouteRegistry method for testing.
10581105
*/

0 commit comments

Comments
 (0)