
com.vaadin.base.devserver.DebugWindowConnection Maven / Gradle / Ivy
/**
* Copyright (C) 2000-2023 Vaadin Ltd
*
* This program is available under Vaadin Commercial License and Service Terms.
*
* See for the full
* license.
*/
package com.vaadin.base.devserver;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.stream.Collectors;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.vaadin.base.devserver.stats.DevModeUsageStatistics;
import com.vaadin.experimental.FeatureFlags;
import com.vaadin.flow.internal.BrowserLiveReload;
import com.vaadin.flow.server.VaadinContext;
import org.atmosphere.cpr.AtmosphereResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import elemental.json.Json;
import elemental.json.JsonObject;
/**
* {@link BrowserLiveReload} implementation class.
*
* For internal use only. May be renamed or removed in a future release.
*
* @author Vaadin Ltd
*
*/
public class DebugWindowConnection implements BrowserLiveReload {
private final ClassLoader classLoader;
private VaadinContext context;
private final ConcurrentLinkedQueue> atmosphereResources = new ConcurrentLinkedQueue<>();
private Backend backend = null;
private static final EnumMap> IDENTIFIER_CLASSES = new EnumMap<>(
Backend.class);
private final ObjectMapper objectMapper = new ObjectMapper();
static {
IDENTIFIER_CLASSES.put(Backend.JREBEL, Collections.singletonList(
"org.zeroturnaround.jrebel.vaadin.JRebelClassEventListener"));
IDENTIFIER_CLASSES.put(Backend.HOTSWAP_AGENT, Collections.singletonList(
"org.hotswap.agent.plugin.vaadin.VaadinIntegration"));
IDENTIFIER_CLASSES.put(Backend.SPRING_BOOT_DEVTOOLS, Arrays.asList(
"com.vaadin.flow.spring.SpringServlet",
"org.springframework.boot.devtools.livereload.LiveReloadServer"));
}
DebugWindowConnection(VaadinContext context) {
this(DebugWindowConnection.class.getClassLoader(), context);
}
DebugWindowConnection(ClassLoader classLoader, VaadinContext context) {
this.classLoader = classLoader;
this.context = context;
}
@Override
public Backend getBackend() {
if (backend != null) {
return backend;
}
for (Map.Entry> entry : IDENTIFIER_CLASSES
.entrySet()) {
Backend backendCandidate = entry.getKey();
boolean found = true;
for (String clazz : entry.getValue()) {
try {
classLoader.loadClass(clazz);
} catch (ClassNotFoundException e) { // NOSONAR
getLogger().debug("Class {} not found, excluding {}", clazz,
backendCandidate);
found = false;
break;
}
}
if (found) {
backend = backendCandidate;
break;
}
}
return backend;
}
@Override
public void setBackend(Backend backend) {
assert (backend != null);
this.backend = backend;
}
@Override
public void onConnect(AtmosphereResource resource) {
resource.suspend(-1);
atmosphereResources.add(new WeakReference<>(resource));
resource.getBroadcaster().broadcast("{\"command\": \"hello\"}",
resource);
send(resource, "serverInfo", new ServerInfo());
send(resource, "featureFlags", new FeatureFlagMessage(FeatureFlags
.get(context).getFeatures().stream()
.filter(feature -> !feature.equals(FeatureFlags.EXAMPLE))
.collect(Collectors.toList())));
}
private void send(AtmosphereResource resource, String command,
Object data) {
try {
resource.getBroadcaster().broadcast(objectMapper.writeValueAsString(
new DebugWindowMessage(command, data)), resource);
} catch (Exception e) {
getLogger().error("Error sending message", e);
}
}
@Override
public void onDisconnect(AtmosphereResource resource) {
if (!atmosphereResources
.removeIf(resourceRef -> resource.equals(resourceRef.get()))) {
String uuid = resource.uuid();
getLogger().warn(
"Push connection {} is not a live-reload connection or already closed",
uuid);
}
}
@Override
public boolean isLiveReload(AtmosphereResource resource) {
return atmosphereResources.stream()
.anyMatch(resourceRef -> resource.equals(resourceRef.get()));
}
@Override
public void reload() {
atmosphereResources.forEach(resourceRef -> {
AtmosphereResource resource = resourceRef.get();
if (resource != null) {
resource.getBroadcaster().broadcast("{\"command\": \"reload\"}",
resource);
}
});
}
@Override
public void onMessage(String message) {
if (message.isEmpty()) {
getLogger().debug("Received live reload heartbeat");
return;
}
JsonObject json = Json.parse(message);
String command = json.getString("command");
if ("setFeature".equals(command)) {
JsonObject data = json.getObject("data");
FeatureFlags.get(context).setEnabled(data.getString("featureId"),
data.getBoolean("enabled"));
} else if ("reportTelemetry".equals(command)) {
JsonObject data = json.getObject("data");
DevModeUsageStatistics.handleBrowserData(data);
}
}
private static Logger getLogger() {
return LoggerFactory.getLogger(DebugWindowConnection.class.getName());
}
}