diff --git a/flow-server/src/main/java/com/vaadin/flow/server/startup/AbstractRouteRegistryInitializer.java b/flow-server/src/main/java/com/vaadin/flow/server/startup/AbstractRouteRegistryInitializer.java index 772dc490558..18c4eeafd82 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/startup/AbstractRouteRegistryInitializer.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/startup/AbstractRouteRegistryInitializer.java @@ -19,8 +19,11 @@ import java.io.Serializable; import java.lang.annotation.Annotation; +import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -34,6 +37,7 @@ import com.vaadin.flow.router.RouteAlias; import com.vaadin.flow.router.RouterLayout; import com.vaadin.flow.router.internal.RouteUtil; +import com.vaadin.flow.server.InvalidRouteConfigurationException; import com.vaadin.flow.server.InvalidRouteLayoutConfigurationException; import com.vaadin.flow.server.PWA; import com.vaadin.flow.server.VaadinContext; @@ -96,6 +100,30 @@ private void checkForConflictingAnnotations(VaadinContext context, } + Route routeAnnotation = route.getAnnotation(Route.class); + RouteAlias[] aliases = route.getAnnotationsByType(RouteAlias.class); + if (routeAnnotation != null && aliases.length > 0) { + Map stats = Arrays.stream(aliases) + .map(RouteAlias::value).collect(Collectors.groupingBy( + Function.identity(), Collectors.counting())); + if (stats.containsKey(routeAnnotation.value())) { + throw new InvalidRouteConfigurationException(String.format( + "'%s' declares '@%s' and '@%s' with the same path '%s'", + route.getCanonicalName(), Route.class.getSimpleName(), + RouteAlias.class.getSimpleName(), + routeAnnotation.value())); + } + String repeatedAliases = stats.entrySet().stream() + .filter(e -> e.getValue() > 1).map(Map.Entry::getKey) + .collect(Collectors.joining(", ")); + if (!repeatedAliases.isEmpty()) { + throw new InvalidRouteConfigurationException(String.format( + "'%s' declares multiple '@%s' with same paths: %s.", + route.getCanonicalName(), + RouteAlias.class.getSimpleName(), repeatedAliases)); + } + } + if (route.isAnnotationPresent(PageTitle.class) && HasDynamicTitle.class.isAssignableFrom(route)) { throw new DuplicateNavigationTitleException(String.format( @@ -110,8 +138,7 @@ private void checkForConflictingAnnotations(VaadinContext context, validateRouteAnnotation(context, route, annotation); - for (RouteAlias alias : route - .getAnnotationsByType(RouteAlias.class)) { + for (RouteAlias alias : aliases) { validateRouteAliasAnnotation(context, route, alias, annotation); } }); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/startup/AbstractRouteRegistryInitializerTest.java b/flow-server/src/test/java/com/vaadin/flow/server/startup/AbstractRouteRegistryInitializerTest.java index 7d82f8d6034..8734d6ccf21 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/startup/AbstractRouteRegistryInitializerTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/startup/AbstractRouteRegistryInitializerTest.java @@ -25,7 +25,9 @@ import com.vaadin.flow.component.Tag; import com.vaadin.flow.router.ParentLayout; import com.vaadin.flow.router.Route; +import com.vaadin.flow.router.RouteAlias; import com.vaadin.flow.router.RouterLayout; +import com.vaadin.flow.server.InvalidRouteConfigurationException; import com.vaadin.flow.server.InvalidRouteLayoutConfigurationException; public class AbstractRouteRegistryInitializerTest { @@ -55,11 +57,59 @@ public static class RouteAndParentRouterLayout extends Component } + @Tag(Tag.DIV) + @Route("foo") + @RouteAlias("foo") + public static class RouteAndAliasWithSamePath extends Component { + + } + + @Tag(Tag.DIV) + @Route("foo") + @RouteAlias("bar") + @RouteAlias("baz") + @RouteAlias("bar") + @RouteAlias("baz") + @RouteAlias("hey") + public static class AliasesWithSamePath extends Component { + + } + @Test(expected = InvalidRouteLayoutConfigurationException.class) public void routeAndParentLayout_notRouterLayout_throws() { initializer.validateRouteClasses(null, Stream.of(RouteAndParentLayout.class)); + } + + @Test + public void validateRouteClasses_samePathForRouteAndAlias_throws() { + InvalidRouteConfigurationException exception = Assert.assertThrows( + InvalidRouteConfigurationException.class, + () -> initializer.validateRouteClasses(null, + Stream.of(RouteAndAliasWithSamePath.class))); + Assert.assertTrue(containsQuotedAnnotationName(exception.getMessage(), + Route.class)); + Assert.assertTrue(containsQuotedAnnotationName(exception.getMessage(), + RouteAlias.class)); + Assert.assertTrue(exception.getMessage().contains("same path")); + Assert.assertTrue(exception.getMessage().contains("foo")); + } + @Test + public void validateRouteClasses_samePathForRepeatableAlias_throws() { + InvalidRouteConfigurationException exception = Assert.assertThrows( + InvalidRouteConfigurationException.class, + () -> initializer.validateRouteClasses(null, + Stream.of(AliasesWithSamePath.class))); + Assert.assertFalse(containsQuotedAnnotationName(exception.getMessage(), + Route.class)); + Assert.assertTrue(containsQuotedAnnotationName(exception.getMessage(), + RouteAlias.class)); + Assert.assertTrue(exception.getMessage().contains("same paths")); + Assert.assertTrue(exception.getMessage().contains("bar")); + Assert.assertTrue(exception.getMessage().contains("baz")); + Assert.assertFalse(exception.getMessage().contains("foo")); + Assert.assertFalse(exception.getMessage().contains("hey")); } @Test @@ -72,4 +122,8 @@ public void routeAndParentLayout_routerLayout_returnsValidatedClass() { classes.iterator().next()); } + private static boolean containsQuotedAnnotationName(String message, + Class clazz) { + return message.contains("'@" + clazz.getSimpleName() + "'"); + } }