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 151e579f452..048576d5696 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 @@ -102,23 +102,27 @@ private void checkForConflictingAnnotations(VaadinContext context, RouteAlias[] aliases = route.getAnnotationsByType(RouteAlias.class); if (aliases.length > 0) { - Route routeAnnotation = route.getAnnotation(Route.class); + String routePath = RouteUtil.getRoutePath(context, route); Map stats = Arrays.stream(aliases) - .map(RouteAlias::value).collect(Collectors.groupingBy( - Function.identity(), Collectors.counting())); - if (stats.containsKey(routeAnnotation.value())) { + .map(ann -> RouteUtil.getRouteAliasPath(route, ann)) + .collect(Collectors.groupingBy(Function.identity(), + Collectors.counting())); + if (stats.containsKey(routePath)) { throw new InvalidRouteConfigurationException(String.format( - "'%s' declares '@%s' and '@%s' with the same path '%s'", + "'%s' declares '@%s' and '@%s' with the same path '%s'. " + + "Make sure paths are different by checking annotation values " + + "and prefixes defined by layouts", route.getCanonicalName(), Route.class.getSimpleName(), - RouteAlias.class.getSimpleName(), - routeAnnotation.value())); + RouteAlias.class.getSimpleName(), routePath)); } 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.", + "'%s' declares multiple '@%s' with same paths: %s." + + "Make sure paths are different by checking annotation values " + + "and prefixes defined by layouts.", route.getCanonicalName(), RouteAlias.class.getSimpleName(), repeatedAliases)); } 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 8734d6ccf21..04acea9127d 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 @@ -20,15 +20,18 @@ import org.junit.Assert; import org.junit.Test; +import org.mockito.Mockito; import com.vaadin.flow.component.Component; 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.RoutePrefix; import com.vaadin.flow.router.RouterLayout; import com.vaadin.flow.server.InvalidRouteConfigurationException; import com.vaadin.flow.server.InvalidRouteLayoutConfigurationException; +import com.vaadin.flow.server.VaadinContext; public class AbstractRouteRegistryInitializerTest { @@ -36,6 +39,8 @@ public class AbstractRouteRegistryInitializerTest { }; + VaadinContext context = Mockito.mock(VaadinContext.class); + @Tag(Tag.DIV) public static class TestParentLayout extends Component implements RouterLayout { @@ -64,6 +69,43 @@ public static class RouteAndAliasWithSamePath extends Component { } + @Tag(Tag.DIV) + @RoutePrefix("parent") + public static class PrefixedParentLayout extends Component + implements RouterLayout { + } + + @Tag(Tag.DIV) + @RoutePrefix("nested") + @ParentLayout(PrefixedParentLayout.class) + public static class NestedPrefixedParentLayout extends Component + implements RouterLayout { + } + + @Tag(Tag.DIV) + @Route("foo") + @RouteAlias(value = "foo", layout = PrefixedParentLayout.class) + public static class RouteAndAliasWithSamePathDifferentLayoutPrefix + extends Component { + + } + + @Tag(Tag.DIV) + @Route(value = "foo", layout = PrefixedParentLayout.class) + @RouteAlias(value = "foo", layout = PrefixedParentLayout.class) + public static class RouteAndAliasWithSamePathSameLayoutPrefix + extends Component { + + } + + @Tag(Tag.DIV) + @Route(value = "foo", layout = NestedPrefixedParentLayout.class) + @RouteAlias(value = "foo", layout = NestedPrefixedParentLayout.class) + public static class RouteAndAliasWithSamePathSameNestedLayoutPrefix + extends Component { + + } + @Tag(Tag.DIV) @Route("foo") @RouteAlias("bar") @@ -77,7 +119,7 @@ public static class AliasesWithSamePath extends Component { @Test(expected = InvalidRouteLayoutConfigurationException.class) public void routeAndParentLayout_notRouterLayout_throws() { - initializer.validateRouteClasses(null, + initializer.validateRouteClasses(context, Stream.of(RouteAndParentLayout.class)); } @@ -85,7 +127,7 @@ public void routeAndParentLayout_notRouterLayout_throws() { public void validateRouteClasses_samePathForRouteAndAlias_throws() { InvalidRouteConfigurationException exception = Assert.assertThrows( InvalidRouteConfigurationException.class, - () -> initializer.validateRouteClasses(null, + () -> initializer.validateRouteClasses(context, Stream.of(RouteAndAliasWithSamePath.class))); Assert.assertTrue(containsQuotedAnnotationName(exception.getMessage(), Route.class)); @@ -99,7 +141,7 @@ public void validateRouteClasses_samePathForRouteAndAlias_throws() { public void validateRouteClasses_samePathForRepeatableAlias_throws() { InvalidRouteConfigurationException exception = Assert.assertThrows( InvalidRouteConfigurationException.class, - () -> initializer.validateRouteClasses(null, + () -> initializer.validateRouteClasses(context, Stream.of(AliasesWithSamePath.class))); Assert.assertFalse(containsQuotedAnnotationName(exception.getMessage(), Route.class)); @@ -112,10 +154,49 @@ public void validateRouteClasses_samePathForRepeatableAlias_throws() { Assert.assertFalse(exception.getMessage().contains("hey")); } + @Test + public void validateRouteClasses_samePathForRouteAndAlias_sameLayoutPrefix_throws() { + InvalidRouteConfigurationException exception = Assert.assertThrows( + InvalidRouteConfigurationException.class, + () -> initializer.validateRouteClasses(context, Stream + .of(RouteAndAliasWithSamePathSameLayoutPrefix.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_samePathForRouteAndAlias_sameNestedLayoutPrefix_throws() { + InvalidRouteConfigurationException exception = Assert.assertThrows( + InvalidRouteConfigurationException.class, + () -> initializer.validateRouteClasses(context, Stream.of( + RouteAndAliasWithSamePathSameNestedLayoutPrefix.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_samePathForRouteAndAlias_differentLayoutPrefix_doNotThrow() { + Set> classes = initializer + .validateRouteClasses(context, Stream.of( + RouteAndAliasWithSamePathDifferentLayoutPrefix.class)); + Assert.assertEquals(1, classes.size()); + Assert.assertEquals( + RouteAndAliasWithSamePathDifferentLayoutPrefix.class, + classes.iterator().next()); + } + @Test public void routeAndParentLayout_routerLayout_returnsValidatedClass() { Set> classes = initializer - .validateRouteClasses(null, + .validateRouteClasses(context, Stream.of(RouteAndParentRouterLayout.class)); Assert.assertEquals(1, classes.size()); Assert.assertEquals(RouteAndParentRouterLayout.class,