diff --git a/packages/java/engine-core/pom.xml b/packages/java/engine-core/pom.xml
index f56bac3d07..8dd756d258 100644
--- a/packages/java/engine-core/pom.xml
+++ b/packages/java/engine-core/pom.xml
@@ -93,6 +93,11 @@
mockito-core
test
+
+ org.mockito
+ mockito-inline
+ test
+
org.hamcrest
hamcrest-all
diff --git a/packages/java/engine-core/src/main/java/com/vaadin/hilla/engine/AotBrowserCallableFinder.java b/packages/java/engine-core/src/main/java/com/vaadin/hilla/engine/AotBrowserCallableFinder.java
index 522694e79a..a06993d42b 100644
--- a/packages/java/engine-core/src/main/java/com/vaadin/hilla/engine/AotBrowserCallableFinder.java
+++ b/packages/java/engine-core/src/main/java/com/vaadin/hilla/engine/AotBrowserCallableFinder.java
@@ -64,10 +64,9 @@ private static String determineApplicationClass(
}
}).filter(Objects::nonNull).findFirst().orElse(null);
if (mainClass == null) {
- LOGGER.warn(
+ LOGGER.debug(
"This project has not been recognized as a Spring Boot"
- + " application because a main class could not be found."
- + " Hilla services will not be available.");
+ + " application because a main class could not be found.");
}
return mainClass;
} catch (NoClassDefFoundError e) {
@@ -102,19 +101,23 @@ private static Path generateAotArtifacts(
var argsFile = engineConfiguration.getBuildDir()
.resolve("hilla-aot-args.txt");
Files.write(argsFile, settings);
-
+ var report = engineConfiguration.getBuildDir()
+ .resolve("hilla-aot-report.txt");
var javaExecutable = ProcessHandle.current().info().command()
.orElse(Path.of(System.getProperty("java.home"), "bin", "java")
.toString());
// Runs the SpringApplicationAotProcessor to generate the
// reflect-config.json file. This comes from the `process-aot` goal.
- int exitCode = new ProcessBuilder().inheritIO()
- .command(javaExecutable, "@" + argsFile).start().waitFor();
+ var process = new ProcessBuilder().inheritIO()
+ .command(javaExecutable, "@" + argsFile)
+ .redirectOutput(report.toFile()).redirectErrorStream(true)
+ .start();
+ int exitCode = process.waitFor();
if (exitCode != 0) {
- LOGGER.error(
- SPRING_AOT_PROCESSOR + " exited with code: " + exitCode);
+ LOGGER.debug(SPRING_AOT_PROCESSOR + " exited with code " + exitCode
+ + ". The output of the process is available in " + report);
}
var json = aotOutput.resolve(Path.of("resources", "META-INF",
diff --git a/packages/java/engine-core/src/main/java/com/vaadin/hilla/engine/EngineConfiguration.java b/packages/java/engine-core/src/main/java/com/vaadin/hilla/engine/EngineConfiguration.java
index 97d08806c4..a72170e1b6 100644
--- a/packages/java/engine-core/src/main/java/com/vaadin/hilla/engine/EngineConfiguration.java
+++ b/packages/java/engine-core/src/main/java/com/vaadin/hilla/engine/EngineConfiguration.java
@@ -16,6 +16,7 @@
import com.vaadin.flow.server.ExecutionFailedException;
import com.vaadin.flow.server.frontend.FrontendUtils;
+import com.vaadin.flow.server.frontend.scanner.ClassFinder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,6 +43,7 @@ public class EngineConfiguration {
private BrowserCallableFinder browserCallableFinder;
private boolean productionMode = false;
private String nodeCommand = "node";
+ private ClassFinder classFinder;
private EngineConfiguration() {
baseDir = Path.of(System.getProperty("user.dir"));
@@ -107,6 +109,10 @@ public String getNodeCommand() {
return nodeCommand;
}
+ public ClassFinder getClassFinder() {
+ return classFinder;
+ }
+
public List> getEndpointAnnotations() {
return parser.getEndpointAnnotations();
}
@@ -129,8 +135,17 @@ public BrowserCallableFinder getBrowserCallableFinder() {
return () -> {
try {
return AotBrowserCallableFinder.findEndpointClasses(this);
- } catch (IOException | InterruptedException e) {
- throw new ExecutionFailedException(e);
+ } catch (Exception e) {
+ if (classFinder != null) {
+ LOGGER.info(
+ "AOT-based detection of browser-callable classes failed."
+ + " Falling back to classpath scan."
+ + " Enable debug logging for more information.");
+ return LookupBrowserCallableFinder
+ .findEndpointClasses(classFinder, this);
+ } else {
+ throw new ExecutionFailedException(e);
+ }
}
};
}
@@ -168,6 +183,7 @@ public Builder(EngineConfiguration configuration) {
this.configuration.browserCallableFinder = configuration.browserCallableFinder;
this.configuration.productionMode = configuration.productionMode;
this.configuration.nodeCommand = configuration.nodeCommand;
+ this.configuration.classFinder = configuration.classFinder;
this.configuration.parser.setEndpointAnnotations(
configuration.getEndpointAnnotations());
this.configuration.parser.setEndpointExposedAnnotations(
@@ -253,6 +269,11 @@ public Builder nodeCommand(String value) {
return this;
}
+ public Builder classFinder(ClassFinder value) {
+ configuration.classFinder = value;
+ return this;
+ }
+
public Builder endpointAnnotations(
Class extends Annotation>... value) {
configuration.parser.setEndpointAnnotations(Arrays.asList(value));
diff --git a/packages/java/engine-core/src/main/java/com/vaadin/hilla/engine/LookupBrowserCallableFinder.java b/packages/java/engine-core/src/main/java/com/vaadin/hilla/engine/LookupBrowserCallableFinder.java
new file mode 100644
index 0000000000..13cbda82b5
--- /dev/null
+++ b/packages/java/engine-core/src/main/java/com/vaadin/hilla/engine/LookupBrowserCallableFinder.java
@@ -0,0 +1,17 @@
+package com.vaadin.hilla.engine;
+
+import java.util.List;
+import java.util.Set;
+
+import com.vaadin.flow.server.frontend.scanner.ClassFinder;
+
+class LookupBrowserCallableFinder {
+
+ static List> findEndpointClasses(ClassFinder classFinder,
+ EngineConfiguration engineConfiguration) {
+ return engineConfiguration.getEndpointAnnotations().stream()
+ .map(classFinder::getAnnotatedClasses).flatMap(Set::stream)
+ .distinct().toList();
+ }
+
+}
diff --git a/packages/java/engine-core/src/test/java/com/vaadin/hilla/engine/EngineConfigurationTest.java b/packages/java/engine-core/src/test/java/com/vaadin/hilla/engine/EngineConfigurationTest.java
new file mode 100644
index 0000000000..6f57a6e694
--- /dev/null
+++ b/packages/java/engine-core/src/test/java/com/vaadin/hilla/engine/EngineConfigurationTest.java
@@ -0,0 +1,61 @@
+package com.vaadin.hilla.engine;
+
+import org.junit.jupiter.api.Test;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import com.vaadin.flow.server.frontend.scanner.ClassFinder;
+
+public class EngineConfigurationTest {
+ @Target(ElementType.TYPE)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface BrowserCallableEndpoint {
+ }
+
+ private static class EndpointFromAot {
+ }
+
+ private static class EndpointFromClassFinder {
+ }
+
+ @Test
+ public void shouldUseAot() throws Exception {
+ var classFinder = mock(ClassFinder.class);
+ when(classFinder
+ .getAnnotatedClasses((Class extends Annotation>) any()))
+ .thenThrow(RuntimeException.class);
+ var conf = new EngineConfiguration.Builder().classFinder(classFinder)
+ .build();
+ try (var aotMock = mockStatic(AotBrowserCallableFinder.class)) {
+ when(AotBrowserCallableFinder.findEndpointClasses(conf))
+ .thenReturn(List.of(EndpointFromAot.class));
+ assertEquals(List.of(EndpointFromAot.class),
+ conf.getBrowserCallableFinder().findBrowserCallables());
+ }
+ }
+
+ @Test
+ public void shouldFallbackToClassFinder() throws Exception {
+ var classFinder = mock(ClassFinder.class);
+ when(classFinder
+ .getAnnotatedClasses((Class extends Annotation>) any()))
+ .thenReturn(Set.of(EndpointFromClassFinder.class));
+ var conf = new EngineConfiguration.Builder().classFinder(classFinder)
+ .endpointAnnotations(BrowserCallableEndpoint.class).build();
+ try (var aotMock = mockStatic(AotBrowserCallableFinder.class)) {
+ when(AotBrowserCallableFinder.findEndpointClasses(conf))
+ .thenThrow(ParserException.class);
+ assertEquals(List.of(EndpointFromClassFinder.class),
+ conf.getBrowserCallableFinder().findBrowserCallables());
+ }
+ }
+}
diff --git a/packages/java/engine-runtime/src/main/java/com/vaadin/hilla/internal/EndpointGeneratorTaskFactoryImpl.java b/packages/java/engine-runtime/src/main/java/com/vaadin/hilla/internal/EndpointGeneratorTaskFactoryImpl.java
index 7bfafa9904..14c55eee02 100644
--- a/packages/java/engine-runtime/src/main/java/com/vaadin/hilla/internal/EndpointGeneratorTaskFactoryImpl.java
+++ b/packages/java/engine-runtime/src/main/java/com/vaadin/hilla/internal/EndpointGeneratorTaskFactoryImpl.java
@@ -97,6 +97,7 @@ private static EngineConfiguration configureFromOptions(Options options) {
.buildDir(options.getBuildDirectoryName())
.outputDir(options.getFrontendGeneratedFolder().toPath())
.nodeCommand(buildTools(options).getNodeExecutable())
+ .classFinder(options.getClassFinder())
.productionMode(options.isProductionMode())
.withDefaultAnnotations().build();
}