From 44d24462c41a4264d94a64cb1866baf9d905052d Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Wed, 19 Feb 2025 09:28:12 +0200 Subject: [PATCH 1/8] feat: improved endpoint detection --- .../TestEndpointGeneratorTaskFactory.java | 12 +++++++++++ .../internal/hilla/EndpointRequestUtil.java | 16 +++++++++++++++ .../server/frontend/BundleValidationUtil.java | 9 ++++++--- .../EndpointGeneratorTaskFactory.java | 20 +++++++++++++++++++ .../flow/server/frontend/FrontendUtils.java | 1 - .../flow/server/frontend/NodeTasks.java | 5 ++++- .../flow/server/frontend/NodeUpdater.java | 4 +++- .../server/frontend/NodeTasksHillaTest.java | 3 ++- .../TestEndpointGeneratorTaskFactory.java | 12 +++++++++++ .../VaadinServletContextInitializer.java | 12 +++++++++-- 10 files changed, 85 insertions(+), 9 deletions(-) diff --git a/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/TestEndpointGeneratorTaskFactory.java b/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/TestEndpointGeneratorTaskFactory.java index 99ac99d3da8..85253e3b646 100644 --- a/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/TestEndpointGeneratorTaskFactory.java +++ b/flow-plugins/flow-maven-plugin/src/test/java/com/vaadin/flow/plugin/maven/TestEndpointGeneratorTaskFactory.java @@ -17,9 +17,11 @@ import java.io.File; import java.io.IOException; +import java.lang.annotation.Annotation; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardOpenOption; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +49,16 @@ public TaskGenerateOpenAPI createTaskGenerateOpenAPI(Options options) { return new TestTaskGenerateOpenAPI(options); } + @Override + public Set> getBrowserCallableAnnotations() { + return Set.of(); + } + + @Override + public boolean hasBrowserCallables(Options options) { + return true; + } + /** * An abstract parent for the test endpoints generator tasks. */ diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/hilla/EndpointRequestUtil.java b/flow-server/src/main/java/com/vaadin/flow/internal/hilla/EndpointRequestUtil.java index 4e6e384ab54..1265df3ca3c 100644 --- a/flow-server/src/main/java/com/vaadin/flow/internal/hilla/EndpointRequestUtil.java +++ b/flow-server/src/main/java/com/vaadin/flow/internal/hilla/EndpointRequestUtil.java @@ -19,6 +19,8 @@ import jakarta.servlet.http.HttpServletRequest; import java.io.Serializable; +import com.vaadin.flow.server.frontend.EndpointGeneratorTaskFactory; +import com.vaadin.flow.server.frontend.Options; import com.vaadin.flow.server.frontend.scanner.ClassFinder; /** @@ -86,4 +88,18 @@ static boolean isHillaAvailable(ClassFinder classFinder) { return false; } } + + /** + * Checks if Hilla is available and Hilla endpoints are used in the project. + * + * @return {@code true} if Hilla is available and Hilla views are used, + * {@code false} otherwise + */ + static boolean areHillaEndpointsUsed(Options options) { + if (!EndpointRequestUtil.isHillaAvailable()) { + return false; + } + return options.getLookup().lookup(EndpointGeneratorTaskFactory.class) + .hasBrowserCallables(options); + } } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/BundleValidationUtil.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/BundleValidationUtil.java index f273961d6b8..99cf650c444 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/BundleValidationUtil.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/BundleValidationUtil.java @@ -30,6 +30,7 @@ import com.vaadin.flow.internal.JacksonUtils; import com.vaadin.flow.internal.StringUtil; import com.vaadin.flow.internal.UsageStatistics; +import com.vaadin.flow.internal.hilla.EndpointRequestUtil; import com.vaadin.flow.server.Constants; import com.vaadin.flow.server.LoadDependenciesOnStartup; import com.vaadin.flow.server.Mode; @@ -68,9 +69,11 @@ public static boolean needsBuild(Options options, try { boolean needsBuild; if (mode.isProduction()) { - if (options.isForceProductionBuild() || FrontendUtils - .isHillaUsed(options.getFrontendDirectory(), - options.getClassFinder())) { + if (options.isForceProductionBuild() + || FrontendUtils.isHillaUsed( + options.getFrontendDirectory(), + options.getClassFinder()) + || EndpointRequestUtil.areHillaEndpointsUsed(options)) { if (options.isForceProductionBuild()) { UsageStatistics.markAsUsed("flow/prod-build-requested", null); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/EndpointGeneratorTaskFactory.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/EndpointGeneratorTaskFactory.java index 177c9d256d4..ca15e383070 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/EndpointGeneratorTaskFactory.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/EndpointGeneratorTaskFactory.java @@ -16,6 +16,9 @@ package com.vaadin.flow.server.frontend; +import java.lang.annotation.Annotation; +import java.util.Set; + /** * A factory for creating Vaadin Endpoint generator tasks. *

@@ -43,4 +46,21 @@ public interface EndpointGeneratorTaskFactory { * @return an endpoint task that generates open api json file. */ TaskGenerateOpenAPI createTaskGenerateOpenAPI(Options options); + + /** + * Fetches all endpoint-type annotations from Hilla configuration + * + * @return Set of endpoint-type annotations + */ + Set> getBrowserCallableAnnotations(); + + /** + * Determines the presence of annotations (e.g. BrowserCallable or Endpoint) + * which require Flow to add Hilla packages to the build. + * + * @param options + * the task options + * @return {@code true} if annotations are present, {@code false} otherwise + */ + boolean hasBrowserCallables(Options options); } diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendUtils.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendUtils.java index 9797292cb43..b5c55659768 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendUtils.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/FrontendUtils.java @@ -40,7 +40,6 @@ import java.util.stream.Stream; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import jakarta.servlet.ServletContext; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java index fc23962ddca..65bc7b4a1e0 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java @@ -73,9 +73,9 @@ public class NodeTasks implements FallibleCommand { TaskGenerateWebComponentBootstrap.class, TaskGenerateFeatureFlags.class, TaskInstallFrontendBuildPlugins.class, + TaskGenerateOpenAPI.class, TaskUpdatePackages.class, TaskRunNpmInstall.class, - TaskGenerateOpenAPI.class, TaskGenerateEndpoint.class, TaskCopyFrontendFiles.class, TaskCopyLocalFrontendFiles.class, @@ -319,6 +319,9 @@ private void addEndpointServicesTasks(Options options) { if (!EndpointRequestUtil.isHillaAvailable(options.getClassFinder())) { return; } + if (!EndpointRequestUtil.areHillaEndpointsUsed(options)) { + return; + } Lookup lookup = options.getLookup(); EndpointGeneratorTaskFactory endpointGeneratorTaskFactory = lookup .lookup(EndpointGeneratorTaskFactory.class); diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeUpdater.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeUpdater.java index 59db588f611..8a5cd8a4672 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeUpdater.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeUpdater.java @@ -43,6 +43,7 @@ import com.vaadin.experimental.FeatureFlags; import com.vaadin.flow.internal.JacksonUtils; import com.vaadin.flow.internal.JsonDecodingException; +import com.vaadin.flow.internal.hilla.EndpointRequestUtil; import com.vaadin.flow.server.Constants; import com.vaadin.flow.server.frontend.scanner.ClassFinder; import com.vaadin.flow.server.frontend.scanner.FrontendDependencies; @@ -620,7 +621,8 @@ private ObjectNode generateVersionsFromPackageJson(JsonNode packageJson) { private void putHillaComponentsDependencies( Map dependencies, String packageJsonKey) { if (FrontendUtils.isHillaUsed(options.getFrontendDirectory(), - options.getClassFinder())) { + options.getClassFinder()) + || EndpointRequestUtil.areHillaEndpointsUsed(options)) { if (options.isReactEnabled()) { dependencies.putAll(readDependenciesIfAvailable( "hilla/components/react", packageJsonKey)); diff --git a/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTasksHillaTest.java b/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTasksHillaTest.java index 65dfbd75839..e697f7fc25f 100644 --- a/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTasksHillaTest.java +++ b/flow-server/src/test/java/com/vaadin/flow/server/frontend/NodeTasksHillaTest.java @@ -122,7 +122,8 @@ public void should_useHillaEngine_whenEnabled() EndpointRequestUtil.class, Mockito.CALLS_REAL_METHODS)) { util.when(() -> EndpointRequestUtil.isHillaAvailable(Mockito.any())) .thenReturn(true); - + util.when(() -> EndpointRequestUtil + .areHillaEndpointsUsed(Mockito.any())).thenReturn(true); new NodeTasks(options).execute(); } diff --git a/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/TestEndpointGeneratorTaskFactory.java b/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/TestEndpointGeneratorTaskFactory.java index bdc758e18dd..f91999291d9 100644 --- a/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/TestEndpointGeneratorTaskFactory.java +++ b/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/TestEndpointGeneratorTaskFactory.java @@ -17,9 +17,11 @@ import java.io.File; import java.io.IOException; +import java.lang.annotation.Annotation; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.StandardOpenOption; +import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +49,16 @@ public TaskGenerateOpenAPI createTaskGenerateOpenAPI(Options options) { return new TestTaskGenerateOpenAPI(options); } + @Override + public Set> getBrowserCallableAnnotations() { + return Set.of(); + } + + @Override + public boolean hasBrowserCallables(Options options) { + return true; + } + /** * An abstract parent for the test endpoints generator tasks. */ diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletContextInitializer.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletContextInitializer.java index 921f5d22d7d..14a42651ca5 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletContextInitializer.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/VaadinServletContextInitializer.java @@ -21,7 +21,6 @@ import jakarta.servlet.ServletContextListener; import jakarta.servlet.ServletException; import jakarta.servlet.annotation.HandlesTypes; - import java.io.File; import java.io.IOException; import java.io.Serializable; @@ -65,6 +64,7 @@ import com.vaadin.flow.di.LookupInitializer; import com.vaadin.flow.internal.DevModeHandlerManager; import com.vaadin.flow.router.HasErrorParameter; +import com.vaadin.flow.router.Layout; import com.vaadin.flow.router.Route; import com.vaadin.flow.router.RouteAlias; import com.vaadin.flow.router.RouteConfiguration; @@ -75,7 +75,7 @@ import com.vaadin.flow.server.RouteRegistry; import com.vaadin.flow.server.VaadinServletContext; import com.vaadin.flow.server.communication.IndexHtmlRequestHandler; -import com.vaadin.flow.router.Layout; +import com.vaadin.flow.server.frontend.EndpointGeneratorTaskFactory; import com.vaadin.flow.server.startup.AbstractRouteRegistryInitializer; import com.vaadin.flow.server.startup.AnnotationValidator; import com.vaadin.flow.server.startup.ApplicationConfiguration; @@ -559,6 +559,14 @@ public void failFastContextInitialized(ServletContextEvent event) collectHandleTypes(devModeHandlerManager.getHandlesTypes(), annotations, superTypes); + EndpointGeneratorTaskFactory endpointGeneratorTaskFactory = lookup + .lookup(EndpointGeneratorTaskFactory.class); + + if (endpointGeneratorTaskFactory != null) { + annotations.addAll(endpointGeneratorTaskFactory + .getBrowserCallableAnnotations()); + } + Set> classes = findClassesForDevMode(basePackages, annotations, superTypes); From 7cf1cf812c672e8dd682f382005d1d955e8bc2c4 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Fri, 21 Feb 2025 08:45:43 +0200 Subject: [PATCH 2/8] fix test --- .../vaadin/flow/plugin/base/BuildFrontendUtilTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/flow-plugins/flow-plugin-base/src/test/java/com/vaadin/flow/plugin/base/BuildFrontendUtilTest.java b/flow-plugins/flow-plugin-base/src/test/java/com/vaadin/flow/plugin/base/BuildFrontendUtilTest.java index 2f0858165ac..4dd8b16a769 100644 --- a/flow-plugins/flow-plugin-base/src/test/java/com/vaadin/flow/plugin/base/BuildFrontendUtilTest.java +++ b/flow-plugins/flow-plugin-base/src/test/java/com/vaadin/flow/plugin/base/BuildFrontendUtilTest.java @@ -156,6 +156,8 @@ public void should_useHillaEngine_withNodeUpdater() EndpointRequestUtil.class, Mockito.CALLS_REAL_METHODS)) { util.when(() -> EndpointRequestUtil.isHillaAvailable(Mockito.any())) .thenReturn(true); + util.when(() -> EndpointRequestUtil + .areHillaEndpointsUsed(Mockito.any())).thenReturn(true); BuildFrontendUtil.runNodeUpdater(adapter); } @@ -168,10 +170,10 @@ public void should_useHillaEngine_withNodeUpdater() // Hilla Engine requires npm install, the order of execution is critical final TaskRunNpmInstall taskRunNpmInstall = construction.constructed() .get(0); - InOrder inOrder = Mockito.inOrder(taskRunNpmInstall, - taskGenerateOpenAPI, taskGenerateEndpoint); - inOrder.verify(taskRunNpmInstall).execute(); + InOrder inOrder = Mockito.inOrder(taskGenerateOpenAPI, + taskRunNpmInstall, taskGenerateEndpoint); inOrder.verify(taskGenerateOpenAPI).execute(); + inOrder.verify(taskRunNpmInstall).execute(); inOrder.verify(taskGenerateEndpoint).execute(); } From d764e8dc70fa9e1aa0167498c5a29021ac27cb54 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Fri, 21 Feb 2025 09:49:49 +0200 Subject: [PATCH 3/8] Add null-check --- .../vaadin/flow/internal/hilla/EndpointRequestUtil.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/hilla/EndpointRequestUtil.java b/flow-server/src/main/java/com/vaadin/flow/internal/hilla/EndpointRequestUtil.java index 1265df3ca3c..5316265fef8 100644 --- a/flow-server/src/main/java/com/vaadin/flow/internal/hilla/EndpointRequestUtil.java +++ b/flow-server/src/main/java/com/vaadin/flow/internal/hilla/EndpointRequestUtil.java @@ -99,7 +99,11 @@ static boolean areHillaEndpointsUsed(Options options) { if (!EndpointRequestUtil.isHillaAvailable()) { return false; } - return options.getLookup().lookup(EndpointGeneratorTaskFactory.class) - .hasBrowserCallables(options); + EndpointGeneratorTaskFactory endpointGeneratorTaskFactory = options + .getLookup().lookup(EndpointGeneratorTaskFactory.class); + if (endpointGeneratorTaskFactory != null) { + return endpointGeneratorTaskFactory.hasBrowserCallables(options); + } + return false; } } From 4ee07a76ce6878ba6a2e4adb22b92f4d6ed93be0 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Fri, 21 Feb 2025 10:34:06 +0200 Subject: [PATCH 4/8] fix tests --- .../java/com/vaadin/base/devserver/DevModeEndpointTest.java | 4 ++++ .../vaadin/base/devserver/startup/DevModeInitializerTest.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/DevModeEndpointTest.java b/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/DevModeEndpointTest.java index 0ec719b9931..433d9c65c0f 100644 --- a/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/DevModeEndpointTest.java +++ b/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/DevModeEndpointTest.java @@ -112,6 +112,8 @@ public void should_generateOpenApi() throws Exception { EndpointRequestUtil.class, Mockito.CALLS_REAL_METHODS)) { util.when(() -> EndpointRequestUtil.isHillaAvailable(Mockito.any())) .thenReturn(true); + util.when(() -> EndpointRequestUtil + .areHillaEndpointsUsed(Mockito.any())).thenReturn(true); devModeStartupListener.onStartup(classes, servletContext); handler = getDevModeHandler(); waitForDevServer(); @@ -144,6 +146,8 @@ public void should_generateTs_files() throws Exception { EndpointRequestUtil.class, Mockito.CALLS_REAL_METHODS)) { util.when(() -> EndpointRequestUtil.isHillaAvailable(Mockito.any())) .thenReturn(true); + util.when(() -> EndpointRequestUtil + .areHillaEndpointsUsed(Mockito.any())).thenReturn(true); devModeStartupListener.onStartup(classes, servletContext); handler = getDevModeHandler(); waitForDevServer(); diff --git a/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/startup/DevModeInitializerTest.java b/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/startup/DevModeInitializerTest.java index ea8a2cbbbf0..577b640e853 100644 --- a/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/startup/DevModeInitializerTest.java +++ b/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/startup/DevModeInitializerTest.java @@ -357,6 +357,8 @@ public void should_generateOpenApi_when_EndpointPresents() EndpointRequestUtil.class, Mockito.CALLS_REAL_METHODS)) { util.when(() -> EndpointRequestUtil .isHillaAvailable(Mockito.any())).thenReturn(true); + util.when(() -> EndpointRequestUtil + .areHillaEndpointsUsed(Mockito.any())).thenReturn(true); devModeStartupListener.onStartup(classes, servletContext); handler = getDevModeHandler(); waitForDevServer(); @@ -425,6 +427,8 @@ public void should_generateTs_files() throws Exception { EndpointRequestUtil.class, Mockito.CALLS_REAL_METHODS)) { util.when(() -> EndpointRequestUtil .isHillaAvailable(Mockito.any())).thenReturn(true); + util.when(() -> EndpointRequestUtil + .areHillaEndpointsUsed(Mockito.any())).thenReturn(true); devModeStartupListener.onStartup(classes, servletContext); handler = getDevModeHandler(); waitForDevServer(); From fbd03688679a9698834fc9cdbf838e8ca0d19ebf Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Fri, 21 Feb 2025 13:27:08 +0200 Subject: [PATCH 5/8] fix review findings --- .../internal/hilla/EndpointRequestUtil.java | 2 +- .../flow/server/frontend/NodeTasks.java | 32 +++++++++---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/flow-server/src/main/java/com/vaadin/flow/internal/hilla/EndpointRequestUtil.java b/flow-server/src/main/java/com/vaadin/flow/internal/hilla/EndpointRequestUtil.java index 5316265fef8..21677f21b8b 100644 --- a/flow-server/src/main/java/com/vaadin/flow/internal/hilla/EndpointRequestUtil.java +++ b/flow-server/src/main/java/com/vaadin/flow/internal/hilla/EndpointRequestUtil.java @@ -92,7 +92,7 @@ static boolean isHillaAvailable(ClassFinder classFinder) { /** * Checks if Hilla is available and Hilla endpoints are used in the project. * - * @return {@code true} if Hilla is available and Hilla views are used, + * @return {@code true} if Hilla is available and Hilla endpoints are used, * {@code false} otherwise */ static boolean areHillaEndpointsUsed(Options options) { diff --git a/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java b/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java index 65bc7b4a1e0..25f76cfbd8c 100644 --- a/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java +++ b/flow-server/src/main/java/com/vaadin/flow/server/frontend/NodeTasks.java @@ -319,22 +319,22 @@ private void addEndpointServicesTasks(Options options) { if (!EndpointRequestUtil.isHillaAvailable(options.getClassFinder())) { return; } - if (!EndpointRequestUtil.areHillaEndpointsUsed(options)) { - return; - } - Lookup lookup = options.getLookup(); - EndpointGeneratorTaskFactory endpointGeneratorTaskFactory = lookup - .lookup(EndpointGeneratorTaskFactory.class); - - if (endpointGeneratorTaskFactory != null) { - TaskGenerateOpenAPI taskGenerateOpenAPI = endpointGeneratorTaskFactory - .createTaskGenerateOpenAPI(options); - commands.add(taskGenerateOpenAPI); - - if (options.getFrontendGeneratedFolder() != null) { - TaskGenerateEndpoint taskGenerateEndpoint = endpointGeneratorTaskFactory - .createTaskGenerateEndpoint(options); - commands.add(taskGenerateEndpoint); + if (EndpointRequestUtil.areHillaEndpointsUsed(options) + || FrontendUtils.isHillaUsed(options.getFrontendDirectory())) { + Lookup lookup = options.getLookup(); + EndpointGeneratorTaskFactory endpointGeneratorTaskFactory = lookup + .lookup(EndpointGeneratorTaskFactory.class); + + if (endpointGeneratorTaskFactory != null) { + TaskGenerateOpenAPI taskGenerateOpenAPI = endpointGeneratorTaskFactory + .createTaskGenerateOpenAPI(options); + commands.add(taskGenerateOpenAPI); + + if (options.getFrontendGeneratedFolder() != null) { + TaskGenerateEndpoint taskGenerateEndpoint = endpointGeneratorTaskFactory + .createTaskGenerateEndpoint(options); + commands.add(taskGenerateEndpoint); + } } } } From 057d60f345bcda23d4d7dea3d12f5fc6248f597e Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Mon, 24 Feb 2025 12:29:43 +0200 Subject: [PATCH 6/8] add test --- .../VaadinServletContextInitializerTest.java | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/vaadin-spring/src/test/java/com/vaadin/flow/spring/VaadinServletContextInitializerTest.java b/vaadin-spring/src/test/java/com/vaadin/flow/spring/VaadinServletContextInitializerTest.java index 5ab4e3ca3d6..73e55fb0f23 100644 --- a/vaadin-spring/src/test/java/com/vaadin/flow/spring/VaadinServletContextInitializerTest.java +++ b/vaadin-spring/src/test/java/com/vaadin/flow/spring/VaadinServletContextInitializerTest.java @@ -3,10 +3,12 @@ import jakarta.servlet.ServletContext; import jakarta.servlet.ServletContextEvent; import jakarta.servlet.ServletContextListener; - +import java.lang.annotation.Annotation; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; @@ -15,6 +17,7 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -24,10 +27,13 @@ import org.springframework.core.env.Environment; import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.dependency.CssImport; +import com.vaadin.flow.component.dependency.NpmPackage; import com.vaadin.flow.di.Lookup; import com.vaadin.flow.function.DeploymentConfiguration; import com.vaadin.flow.internal.DevModeHandler; import com.vaadin.flow.internal.DevModeHandlerManager; +import com.vaadin.flow.internal.hilla.EndpointRequestUtil; import com.vaadin.flow.router.BeforeEnterEvent; import com.vaadin.flow.router.ErrorParameter; import com.vaadin.flow.router.HasErrorParameter; @@ -35,6 +41,10 @@ import com.vaadin.flow.router.RouteNotFoundError; import com.vaadin.flow.server.VaadinContext; import com.vaadin.flow.server.VaadinServletContext; +import com.vaadin.flow.server.frontend.EndpointGeneratorTaskFactory; +import com.vaadin.flow.server.frontend.Options; +import com.vaadin.flow.server.frontend.TaskGenerateEndpoint; +import com.vaadin.flow.server.frontend.TaskGenerateOpenAPI; import com.vaadin.flow.server.startup.ApplicationConfiguration; import com.vaadin.flow.server.startup.ApplicationRouteRegistry; import com.vaadin.flow.server.startup.ServletDeployer; @@ -139,6 +149,70 @@ public void onStartup_devModeAlreadyInitialized_devModeInitializationSkipped() Mockito.any(VaadinContext.class)); } + @Test + public void hillaProvidesAnnotations_checkTheyAreAdded() throws Exception { + initDefaultMocks(); + + DevModeHandler devModeHandler = Mockito.mock(DevModeHandler.class); + Mockito.when(devModeHandlerManager.getDevModeHandler()) + .thenReturn(null); + Mockito.when(devModeHandlerManager.getHandlesTypes()) + .thenReturn(new Class[0]); + + Mockito.when(appConfig.frontendHotdeploy()).thenReturn(true); + + Mockito.when(servletContext.getAttribute(Lookup.class.getName())) + .thenReturn(lookup); + + final Set> annotations = Set + .of(NpmPackage.class, CssImport.class); + + Mockito.doReturn(new EndpointGeneratorTaskFactory() { + @Override + public TaskGenerateEndpoint createTaskGenerateEndpoint( + Options options) { + return null; + } + + @Override + public TaskGenerateOpenAPI createTaskGenerateOpenAPI( + Options options) { + return null; + } + + @Override + public Set> getBrowserCallableAnnotations() { + // Not really relevant which annotations are used here. + // Can't use the real Hilla annotations anyway. + return annotations; + } + + @Override + public boolean hasBrowserCallables(Options options) { + return true; + } + }).when(lookup).lookup(EndpointGeneratorTaskFactory.class); + + try (MockedStatic util = Mockito.mockStatic( + EndpointRequestUtil.class, Mockito.CALLS_REAL_METHODS)) { + util.when(() -> EndpointRequestUtil.isHillaAvailable(Mockito.any())) + .thenReturn(true); + util.when(() -> EndpointRequestUtil + .areHillaEndpointsUsed(Mockito.any())).thenReturn(true); + } + + VaadinServletContextInitializer initializer = getStubbedVaadinServletContextInitializer(); + initializer.onStartup(servletContext); + + ArgumentCaptor>> captor = ArgumentCaptor + .forClass(List.class); + Mockito.verify(initializer, Mockito.atMostOnce()).findClassesForDevMode( + Mockito.any(), captor.capture(), Mockito.any()); + List> captured = captor.getValue(); + + Assert.assertTrue(captured.containsAll(annotations)); + } + @Test public void errorParameterServletContextListenerEvent_hasCustomRouteNotFoundViewExtendingRouteNotFoundError_customRouteNotFoundViewIsRegistered() throws Exception { From 7299c6350054e502865a1478630951f3a3ce7638 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Mon, 24 Feb 2025 14:30:13 +0200 Subject: [PATCH 7/8] add hilla annotations to DevModeClassFinder --- .../devserver/startup/DevModeInitializer.java | 23 ++++++++++++++++--- .../startup/DevModeClassFinderTest.java | 5 ++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/startup/DevModeInitializer.java b/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/startup/DevModeInitializer.java index 9d63cee4bbd..0d64a9f5d40 100644 --- a/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/startup/DevModeInitializer.java +++ b/vaadin-dev-server/src/main/java/com/vaadin/base/devserver/startup/DevModeInitializer.java @@ -67,6 +67,7 @@ import com.vaadin.flow.server.Mode; import com.vaadin.flow.server.VaadinContext; import com.vaadin.flow.server.VaadinServlet; +import com.vaadin.flow.server.frontend.EndpointGeneratorTaskFactory; import com.vaadin.flow.server.frontend.FrontendUtils; import com.vaadin.flow.server.frontend.NodeTasks; import com.vaadin.flow.server.frontend.Options; @@ -98,9 +99,12 @@ static class DevModeClassFinder extends DefaultClassFinder { private static final Set APPLICABLE_CLASS_NAMES = Collections .unmodifiableSet(calculateApplicableClassNames()); + private final Set> browserCallableAnnotations; - public DevModeClassFinder(Set> classes) { + public DevModeClassFinder(Set> classes, + Set> browserCallableAnnotations) { super(classes); + this.browserCallableAnnotations = browserCallableAnnotations; } @Override @@ -117,7 +121,8 @@ public Set> getSubTypesOf(Class type) { } private void ensureImplementation(Class clazz) { - if (!APPLICABLE_CLASS_NAMES.contains(clazz.getName())) { + if (!APPLICABLE_CLASS_NAMES.contains(clazz.getName()) + && !browserCallableAnnotations.contains(clazz)) { throw new IllegalArgumentException("Unexpected class name " + clazz + ". Implementation error: the class finder " + "instance is not aware of this class. " @@ -127,6 +132,7 @@ private void ensureImplementation(Class clazz) { } private static Set calculateApplicableClassNames() { + HandlesTypes handlesTypes = DevModeStartupListener.class .getAnnotation(HandlesTypes.class); return Stream.of(handlesTypes.value()).map(Class::getName) @@ -238,7 +244,18 @@ public static DevModeHandler initDevModeHandler(Set> classes, File frontendFolder = config.getFrontendFolder(); Lookup lookupFromContext = context.getAttribute(Lookup.class); - Lookup lookupForClassFinder = Lookup.of(new DevModeClassFinder(classes), + + EndpointGeneratorTaskFactory endpointGeneratorTaskFactory = lookupFromContext + .lookup(EndpointGeneratorTaskFactory.class); + + Set> browserCallableAnnotations = new HashSet<>(); + if (endpointGeneratorTaskFactory != null) { + browserCallableAnnotations.addAll(endpointGeneratorTaskFactory + .getBrowserCallableAnnotations()); + } + + Lookup lookupForClassFinder = Lookup.of( + new DevModeClassFinder(classes, browserCallableAnnotations), ClassFinder.class); Lookup lookup = Lookup.compose(lookupForClassFinder, lookupFromContext); Options options = new Options(lookup, baseDir) diff --git a/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/startup/DevModeClassFinderTest.java b/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/startup/DevModeClassFinderTest.java index d0bdce40978..df4c2687e81 100644 --- a/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/startup/DevModeClassFinderTest.java +++ b/vaadin-dev-server/src/test/java/com/vaadin/base/devserver/startup/DevModeClassFinderTest.java @@ -20,6 +20,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -55,7 +56,7 @@ public class DevModeClassFinderTest { private DevModeClassFinder classFinder = new DevModeClassFinder( - Collections.emptySet()); + Collections.emptySet(), Collections.emptySet()); @Test public void applicableClasses_knownClasses() { @@ -127,7 +128,7 @@ public void callGetgetAnnotatedClassesByName_unexpectedType_throw() @Test(expected = IllegalArgumentException.class) public void callGetSubTypesOfByClass_unexpectedType_throw() { DevModeClassFinder classFinder = new DevModeClassFinder( - Collections.emptySet()); + Collections.emptySet(), Collections.emptySet()); classFinder.getSubTypesOf(Object.class); } From dc67e739680a85a9e81c951a57fc86b2a0d596d2 Mon Sep 17 00:00:00 2001 From: Teppo Kurki Date: Mon, 24 Feb 2025 16:54:04 +0200 Subject: [PATCH 8/8] attempt for spring test fix --- .../com/vaadin/flow/spring/SpringBootAutoConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringBootAutoConfiguration.java b/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringBootAutoConfiguration.java index 32506141da0..4412b029c16 100644 --- a/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringBootAutoConfiguration.java +++ b/vaadin-spring/src/main/java/com/vaadin/flow/spring/SpringBootAutoConfiguration.java @@ -47,7 +47,7 @@ * @author Vaadin Ltd * */ -@Configuration +@Configuration(proxyBeanMethods = false) @AutoConfigureBefore(WebMvcAutoConfiguration.class) @ConditionalOnClass(ServletContextInitializer.class) @EnableConfigurationProperties(VaadinConfigurationProperties.class)