All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.vaadin.base.devserver.DebugWindowConnection Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2000-2024 Vaadin Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.vaadin.base.devserver;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.atmosphere.cpr.AtmosphereResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.vaadin.base.devserver.stats.DevModeUsageStatistics;
import com.vaadin.experimental.FeatureFlags;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.internal.BrowserLiveReload;
import com.vaadin.flow.server.DevToolsToken;
import com.vaadin.flow.server.VaadinContext;
import com.vaadin.flow.server.communication.AtmospherePushConnection.FragmentedMessage;
import com.vaadin.pro.licensechecker.BuildType;
import com.vaadin.pro.licensechecker.LicenseChecker;
import com.vaadin.pro.licensechecker.Product;

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 ConcurrentHashMap, FragmentedMessage> resources = new ConcurrentHashMap<>(); private Backend backend = null; private static final EnumMap> IDENTIFIER_CLASSES = new EnumMap<>( Backend.class); private final ObjectMapper objectMapper = new ObjectMapper(); private List plugins; static { IDENTIFIER_CLASSES.put(Backend.JREBEL, Collections.singletonList( "org.zeroturnaround.jrebel.vaadin.JRebelInitializer")); 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; findPlugins(); } private void findPlugins() { ServiceLoader loader = ServiceLoader .load(DevToolsMessageHandler.class, classLoader); this.plugins = new ArrayList<>(); for (DevToolsMessageHandler s : loader) { this.plugins.add(s); } } @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; } /** Implementation of the development tools interface. */ public static class DevToolsInterfaceImpl implements DevToolsInterface { private DebugWindowConnection debugWindowConnection; private AtmosphereResource resource; private DevToolsInterfaceImpl( DebugWindowConnection debugWindowConnection, AtmosphereResource resource) { this.debugWindowConnection = debugWindowConnection; this.resource = resource; } @Override public void send(String command, JsonObject data) { JsonObject msg = Json.createObject(); msg.put("command", command); if (data != null) { msg.put("data", data); } debugWindowConnection.send(resource, msg.toJson()); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } DevToolsInterfaceImpl that = (DevToolsInterfaceImpl) o; return Objects.equals(debugWindowConnection, that.debugWindowConnection) && Objects.equals(resource, that.resource); } @Override public int hashCode() { return Objects.hash(debugWindowConnection, resource); } } protected DevToolsInterface getDevToolsInterface( AtmosphereResource resource) { return new DevToolsInterfaceImpl(this, resource); } @Override public void onConnect(AtmosphereResource resource) { if (DevToolsToken.getToken() .equals(resource.getRequest().getParameter("token"))) { handleConnect(resource); } else { getLogger().debug( "Connection denied because of a missing or invalid token. Either the host is not on the 'vaadin.devmode.hosts-allowed' list or it is using an outdated token"); try { resource.close(); } catch (IOException e) { getLogger().debug( "Error closing the denied websocket connection", e); } } } private void handleConnect(AtmosphereResource resource) { resource.suspend(-1); resources.put(new WeakReference<>(resource), new FragmentedMessage()); resource.getBroadcaster().broadcast("{\"command\": \"hello\"}", resource); for (DevToolsMessageHandler plugin : plugins) { plugin.handleConnect(getDevToolsInterface(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 { send(resource, objectMapper .writeValueAsString(new DebugWindowMessage(command, data))); } catch (Exception e) { getLogger().error("Error sending message", e); } } private void send(AtmosphereResource resource, String json) { resource.getBroadcaster().broadcast(json, resource); } @Override public void onDisconnect(AtmosphereResource resource) { for (DevToolsMessageHandler plugin : plugins) { plugin.handleDisconnect(getDevToolsInterface(resource)); } if (!resources.keySet() .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 getRef(resource) != null; } /** * Broadcasts the given message to all connected clients. * * @param msg * the message to broadcast */ public void broadcast(JsonObject msg) { resources.keySet().forEach(resourceRef -> { AtmosphereResource resource = resourceRef.get(); if (resource != null) { resource.getBroadcaster().broadcast(msg.toJson(), resource); } }); } @Override public void reload() { JsonObject msg = Json.createObject(); msg.put("command", "reload"); broadcast(msg); } @Override public void refresh(boolean refreshLayouts) { JsonObject msg = Json.createObject(); msg.put("command", "reload"); msg.put("strategy", refreshLayouts ? "full-refresh" : "refresh"); broadcast(msg); } @Override public void update(String path, String content) { JsonObject msg = Json.createObject(); msg.put("command", "update"); msg.put("path", path); msg.put("content", content); broadcast(msg); } @SuppressWarnings("FutureReturnValueIgnored") @Override public void onMessage(AtmosphereResource resource, String message) { if (message.isEmpty()) { getLogger().debug("Received live reload heartbeat"); return; } JsonObject json = Json.parse(message); String command = json.getString("command"); JsonObject data = json.getObject("data"); if ("setFeature".equals(command)) { FeatureFlags.get(context).setEnabled(data.getString("featureId"), data.getBoolean("enabled")); } else if ("reportTelemetry".equals(command)) { DevModeUsageStatistics.handleBrowserData(data); } else if ("checkLicense".equals(command)) { String name = data.getString("name"); String version = data.getString("version"); Product product = new Product(name, version); boolean ok; String errorMessage = ""; try { LicenseChecker.checkLicense(product.getName(), product.getVersion(), BuildType.DEVELOPMENT, keyUrl -> { send(resource, "license-check-nokey", new ProductAndMessage(product, keyUrl)); }); ok = true; } catch (Exception e) { ok = false; errorMessage = e.getMessage(); } if (ok) { send(resource, "license-check-ok", product); } else { ProductAndMessage pm = new ProductAndMessage(product, errorMessage); send(resource, "license-check-failed", pm); } } else { boolean handled = false; for (DevToolsMessageHandler plugin : plugins) { handled = plugin.handleMessage(command, data, getDevToolsInterface(resource)); if (handled) { break; } } if (!handled && command != null && !command.startsWith("copilot-")) { getLogger() .info("Unknown command from the browser: " + command); } } } private static Logger getLogger() { return LoggerFactory.getLogger(DebugWindowConnection.class.getName()); } @Override public FragmentedMessage getOrCreateFragmentedMessage( AtmosphereResource resource) { WeakReference ref = getRef(resource); if (ref == null) { throw new IllegalStateException( "Tried to create a fragmented message for a non-existing resource"); } return resources.get(ref); } private WeakReference getRef( AtmosphereResource resource) { return resources.keySet().stream() .filter(resourceRef -> resource.equals(resourceRef.get())) .findFirst().orElse(null); } @Override public void clearFragmentedMessage(AtmosphereResource resource) { WeakReference ref = getRef(resource); if (ref == null) { getLogger().debug( "Tried to clear the fragmented message for a non-existing resource: {}", resource); return; } resources.put(ref, new FragmentedMessage()); } @Override public void sendHmrEvent(String event, JsonObject eventData) { JsonObject msg = Json.createObject(); msg.put("command", "hmr"); JsonObject data = Json.createObject(); msg.put("data", data); data.put("event", event); data.put("eventData", eventData); broadcast(msg); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy