com.github.mcollovati.vertx.vaadin.VaadinVerticle Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of vertx-vaadin-flow Show documentation
Show all versions of vertx-vaadin-flow Show documentation
An adapter to run Vaadin Flow applications on Vertx
The newest version!
/*
* The MIT License
* Copyright © 2016-2020 Marco Collovati ([email protected])
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.github.mcollovati.vertx.vaadin;
import jakarta.servlet.ServletContainerInitializer;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.HandlesTypes;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.vaadin.base.devserver.startup.DevModeStartupListener;
import com.vaadin.flow.function.DeploymentConfiguration;
import com.vaadin.flow.internal.DevModeHandler;
import com.vaadin.flow.internal.DevModeHandlerManager;
import com.vaadin.flow.server.InitParameters;
import com.vaadin.flow.server.startup.AnnotationValidator;
import com.vaadin.flow.server.startup.ErrorNavigationTargetInitializer;
import com.vaadin.flow.server.startup.LookupServletContainerInitializer;
import com.vaadin.flow.server.startup.RouteRegistryInitializer;
import com.vaadin.flow.server.startup.VaadinAppShellInitializer;
import com.vaadin.flow.server.startup.WebComponentConfigurationRegistryInitializer;
import com.vaadin.flow.server.startup.WebComponentExporterAwareValidator;
import com.vaadin.flow.shared.ApplicationConstants;
import com.vaadin.hilla.startup.EndpointsValidator;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ClassInfo;
import io.github.classgraph.ClassInfoList;
import io.github.classgraph.ScanResult;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.mcollovati.vertx.support.StartupContext;
import com.github.mcollovati.vertx.support.VaadinPatches;
import com.github.mcollovati.vertx.vaadin.connect.VertxEndpointRegistryInitializer;
import com.github.mcollovati.vertx.vaadin.devserver.VertxDevModeHandlerManager;
import static java.util.Arrays.asList;
/**
* Created by marco on 16/07/16.
*/
public class VaadinVerticle extends AbstractVerticle {
static {
VaadinPatches.patch();
}
private static final Logger log = LoggerFactory.getLogger(VaadinVerticle.class);
private HttpServer httpServer;
private VertxVaadinService vaadinService;
@Override
public void start(Promise startFuture) throws Exception {
log.info("Starting vaadin verticle " + getClass().getName());
prepareConfig()
.compose(vaadinOptions -> StartupContext.of(vertx, vaadinOptions))
.compose(this::initVertxVaadin)
.compose(this::startupHttpServer)
.map(router -> {
serviceInitialized(vaadinService, router);
return null;
})
.onComplete(startFuture);
// Perform potential synchronous startup tasks
start();
}
private Future startupHttpServer(VertxVaadin vertxVaadin) {
String mountPoint = vertxVaadin.config().mountPoint();
Router router = Router.router(vertx);
if (vertxVaadin.config().hillaEnabled()) {
String connectEndpoint = vertxVaadin.config().hillaEndpoint();
if (!connectEndpoint.endsWith("/*")) {
connectEndpoint = connectEndpoint.replaceFirst("/?$", "/*");
router.route(connectEndpoint).subRouter(vertxVaadin.connectRouter());
}
}
if (!mountPoint.endsWith("/*")) {
mountPoint = mountPoint.replaceFirst("/?$", "/*");
}
router.route(mountPoint).subRouter(vertxVaadin.router());
log.debug("Mounted Vaadin router on {}", mountPoint);
HttpServerOptions serverOptions =
new HttpServerOptions(config().getJsonObject("server", new JsonObject()))
.setCompressionSupported(true)
.addWebSocketSubProtocol("vite-hmr");
httpServer = vertx.createHttpServer(serverOptions).requestHandler(router);
vertxVaadin.initDevServerWebSocketProxy(httpServer);
Promise promise = Promise.promise();
httpPort()
.flatMap(port -> {
Promise p = Promise.promise();
httpServer.listen(port, p);
return p.future();
})
.compose(
srv -> {
log.info(
"Started Vaadin verticle {} on port {}",
getClass().getName(),
srv.actualPort());
return Future.succeededFuture(router);
},
t -> {
log.error("Cannot start http server", t);
return Future.failedFuture(t);
})
.onComplete(promise);
return promise.future();
}
private Future httpPort() {
Promise promise = Promise.promise();
// search first new port key server.port, then, for backward compatibility
// fallback to httpPort
Integer httpPort = config().getJsonObject("server", new JsonObject())
.getInteger("port", config().getInteger("httpPort", 8080));
if (httpPort == 0) {
try (ServerSocket socket = new ServerSocket(0)) {
promise.complete(socket.getLocalPort());
} catch (Exception e) {
promise.fail(e);
}
} else {
promise.complete(httpPort);
}
return promise.future();
}
protected VertxVaadin createVertxVaadin(StartupContext startupContext) {
return VertxVaadin.create(vertx, startupContext);
}
protected void serviceInitialized(VertxVaadinService service, Router router) {
// empty by default
}
protected Future prepareConfig() {
JsonObject cfgBackend = new JsonObject();
VaadinVerticleConfiguration vaadinVerticleConfiguration =
getClass().getAnnotation(VaadinVerticleConfiguration.class);
Optional.ofNullable(vaadinVerticleConfiguration)
.map(VaadinVerticleConfiguration::serviceName)
.ifPresent(serviceName -> cfgBackend.put("serviceName", serviceName));
cfgBackend.put(
"mountPoint",
Optional.ofNullable(vaadinVerticleConfiguration)
.map(VaadinVerticleConfiguration::mountPoint)
.orElse("/"));
Optional.ofNullable(vaadinVerticleConfiguration)
.map(VaadinVerticleConfiguration::basePackages)
.map(pkgs -> new JsonArray(asList(pkgs)))
.ifPresent(pkgs -> cfgBackend.put("flowBasePackages", pkgs));
cfgBackend.mergeIn(config().getJsonObject("vaadin", new JsonObject()));
String mountPoint = cfgBackend.getString("mountPoint");
cfgBackend.put(ApplicationConstants.CONTEXT_ROOT_URL, mountPoint);
// cfgBackend.put(InitParameters.SERVLET_PARAMETER_PUSH_URL, mountPoint);
cfgBackend.put(InitParameters.DISABLE_AUTOMATIC_SERVLET_REGISTRATION, true);
return Future.succeededFuture(new VaadinOptions(cfgBackend));
}
@Override
public void stop(Promise stopFuture) {
log.info("Stopping vaadin verticle " + getClass().getName());
try {
vaadinService.destroy();
} catch (Exception ex) {
log.error("Error during Vaadin service destroy", ex);
}
DevModeHandler handler =
DevModeHandlerManager.getDevModeHandler(vaadinService).orElse(null);
if (handler != null && !vaadinService.getDeploymentConfiguration().reuseDevServer()) {
handler.stop();
}
try {
// Perform potential synchronous clean-up tasks
stop();
} catch (Exception ex) {
log.error("Error during Verticle synchronous stop", ex);
}
httpServer.close(stopFuture);
log.info("Stopped vaadin verticle " + getClass().getName());
}
private Future initVertxVaadin(StartupContext startupContext) {
VaadinOptions vaadinOpts = startupContext.vaadinOptions();
List pkgs = vaadinOpts.flowBasePackages();
if (!pkgs.isEmpty()) {
pkgs.add("com.vaadin");
pkgs.add("com.github.mcollovati.vertx.vaadin");
}
boolean isDebug = vaadinOpts.debug();
Map, Set>> map = new HashMap<>();
log.debug("Scanning packages {}", String.join(", ", pkgs));
Promise promise = Promise.promise();
vertx.executeBlocking(
event -> {
ClassGraph classGraph = new ClassGraph();
if (isDebug) {
classGraph.verbose();
}
classGraph
.enableClassInfo()
.ignoreClassVisibility()
.enableAnnotationInfo()
.acceptPackages(pkgs.toArray(new String[0]))
// .ignoreParentClassLoaders()
.removeTemporaryFilesAfterScan();
try (ScanResult scanResult = classGraph.scan()) {
boolean haSockJS = scanResult.getClassInfo(
"com.github.mcollovati.vertx.vaadin.sockjs.communication.SockJSPushConnection")
!= null;
vaadinOpts.sockJSSupport(haSockJS);
ClassInfo hillaClassInfo = scanResult.getClassInfo("dev.hilla.EndpointRegistry");
boolean hasHilla = hillaClassInfo != null && !hillaClassInfo.isExternalClass();
if (!hasHilla) {
vaadinOpts.disableHilla();
}
ClassInfo devServerClassInfo =
scanResult.getClassInfo("com.vaadin.base.devserver.startup.DevModeStartupListener");
boolean hasDevServer = devServerClassInfo != null && !devServerClassInfo.isExternalClass();
if (hasDevServer) {
VertxDevModeHandlerManager.patchViteHandler();
} else {
vaadinOpts.disableDevServer();
}
map.putAll(seekRequiredClasses(scanResult, vaadinOpts));
}
try {
Set> lookupInitializerClasses = map.get(LookupServletContainerInitializer.class);
new LookupServletContainerInitializer()
.process(lookupInitializerClasses, startupContext.servletContext());
finalizeVaadinConfig(startupContext);
Promise initializerFuture = Promise.promise();
runInitializers(startupContext, initializerFuture, map);
initializerFuture
.future()
.map(unused -> {
VertxVaadin vertxVaadin = createVertxVaadin(startupContext);
vaadinService = vertxVaadin.vaadinService();
return vertxVaadin;
})
.onComplete(event);
} catch (ServletException ex) {
event.fail(ex);
}
},
promise);
return promise.future();
}
private void finalizeVaadinConfig(StartupContext startupContext) {
DeploymentConfiguration deploymentConfiguration = startupContext.deploymentConfiguration();
startupContext.vaadinOptions().update(deploymentConfiguration.getInitParameters());
}
private void runInitializers(
StartupContext startupContext, Promise promise, Map, Set>> classes) {
Function>> initializerFactory = initializer -> event2 -> {
try {
initializer.onStartup(classes.get(initializer.getClass()), startupContext.servletContext());
event2.complete();
} catch (Exception ex) {
event2.fail(ex);
}
};
List list = new ArrayList<>(asList(
runInitializer(initializerFactory.apply(new RouteRegistryInitializer())),
runInitializer(initializerFactory.apply(new ErrorNavigationTargetInitializer())),
runInitializer(initializerFactory.apply(new WebComponentConfigurationRegistryInitializer())),
runInitializer(initializerFactory.apply(new AnnotationValidator())),
runInitializer(initializerFactory.apply(new WebComponentExporterAwareValidator())),
runInitializer(initializerFactory.apply(new VaadinAppShellInitializer()))));
VaadinOptions vaadinOptions = startupContext.vaadinOptions();
if (vaadinOptions.devServerEnabled()) {
list.add(runInitializer(initializerFactory.apply(new DevModeStartupListener())));
}
if (vaadinOptions.hillaEnabled()) {
list.add(runInitializer(initializerFactory.apply(new VertxEndpointRegistryInitializer())));
}
CompositeFuture.join(list).onComplete(event2 -> {
if (event2.succeeded()) {
promise.complete();
} else {
promise.fail(event2.cause());
}
});
}
private Map, Set>> seekRequiredClasses(ScanResult scanResult, VaadinOptions options) {
Set> initializers = new HashSet<>(Set.of(
RouteRegistryInitializer.class,
AnnotationValidator.class,
ErrorNavigationTargetInitializer.class,
WebComponentConfigurationRegistryInitializer.class,
WebComponentExporterAwareValidator.class,
VaadinAppShellInitializer.class,
LookupServletContainerInitializer.class));
if (options.devServerEnabled()) {
initializers.add(DevModeStartupListener.class);
}
if (options.hillaEnabled()) {
initializers.add(EndpointsValidator.class);
initializers.add(VertxEndpointRegistryInitializer.class);
}
Map, Set>> map = new HashMap<>();
initializers.forEach(type -> registerHandledTypes(scanResult, type, map));
if (!options.devServerEnabled()) {
map.replaceAll((k, v) -> {
v.remove(VertxLookupInitializer.class);
return v;
});
}
return map;
}
private void registerHandledTypes(
ScanResult scanResult, Class> initializerClass, Map, Set>> map) {
HandlesTypes handledTypes = initializerClass.getAnnotation(HandlesTypes.class);
if (handledTypes != null) {
Function, ClassInfoList> classFinder = type -> {
if (type.isAnnotation()) {
return scanResult.getClassesWithAnnotation(type.getCanonicalName());
} else if (type.isInterface()) {
return scanResult.getClassesImplementing(type.getCanonicalName());
} else {
return scanResult.getSubclasses(type.getCanonicalName());
}
};
Set> classes = Stream.of(handledTypes.value())
.map(classFinder)
.flatMap(c -> c.loadClasses().stream())
.collect(Collectors.toSet());
if (!classes.isEmpty()) {
map.put(initializerClass, classes);
}
}
}
private Future runInitializer(Handler> op) {
Promise promise = Promise.promise();
context.executeBlocking(op, promise);
return promise.future();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy