diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/LiveProcessCommandElement.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/LiveProcessCommandElement.java index 71f3ed125..3348895a9 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/LiveProcessCommandElement.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/LiveProcessCommandElement.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.Map; import java.util.Random; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jface.resource.ImageDescriptor; @@ -94,8 +95,14 @@ public class LiveProcessCommandElement extends QuickAccessElement { commandParams.setArguments(arguments); - CompletableFuture.allOf(usedLanguageServers.stream().map(ls -> - ls.getWorkspaceService().executeCommand(commandParams)).toArray(CompletableFuture[]::new)).join(); + try { + CompletableFuture.allOf(usedLanguageServers.stream().map(ls -> + ls.getWorkspaceService().executeCommand(commandParams)).toArray(CompletableFuture[]::new)).get(2, TimeUnit.SECONDS); + } + catch (Exception e) { + // TODO: better exception handling + e.printStackTrace(); + } } } diff --git a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/LiveProcessCommandsQuickAccessProvider.java b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/LiveProcessCommandsQuickAccessProvider.java index 51e319a62..941d7a2c9 100644 --- a/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/LiveProcessCommandsQuickAccessProvider.java +++ b/eclipse-language-servers/org.springframework.tooling.boot.ls/src/org/springframework/tooling/boot/ls/commands/LiveProcessCommandsQuickAccessProvider.java @@ -15,6 +15,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jdt.annotation.NonNull; @@ -59,9 +60,16 @@ public class LiveProcessCommandsQuickAccessProvider implements IQuickAccessCompu commandParams.setCommand(LiveProcessCommandElement.COMMAND_LIST_PROCESSES); List res = Collections.synchronizedList(new ArrayList<>()); - CompletableFuture.allOf(usedLanguageServers.stream().map(ls -> - ls.getWorkspaceService().executeCommand(commandParams).thenAcceptAsync(commandResult -> - createCommandItems(res, commandResult))).toArray(CompletableFuture[]::new)).join(); + + try { + CompletableFuture.allOf(usedLanguageServers.stream().map(ls -> + ls.getWorkspaceService().executeCommand(commandParams).thenAcceptAsync(commandResult -> + createCommandItems(res, commandResult))).toArray(CompletableFuture[]::new)).get(2000, TimeUnit.MILLISECONDS); + } + catch (Exception e) { + // TODO: better error handling + e.printStackTrace(); + } return res.toArray(new QuickAccessElement[res.size()]); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java index 5030f0471..6bab65c70 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java @@ -107,7 +107,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent private final ProjectBasedPropertyIndexProvider adHocPropertyIndexProvider; private final SpringProcessLiveDataProvider liveDataProvider; - private final SpringProcessConnectorService liveDataConnector; + private final SpringProcessConnectorService liveDataService; private final SpringLiveChangeDetectionWatchdog liveChangeDetectionWatchdog; private final ProjectObserver projectObserver; @@ -153,47 +153,36 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent workspaceService.onWorkspaceSymbol(new BootJavaWorkspaceSymbolHandler(indexer, new LiveAppURLSymbolProvider(runningAppProvider))); + + // + // live data component wiring + // + // central live data components (to coordinate live data flow) liveDataProvider = new SpringProcessLiveDataProvider(); + liveDataService = new SpringProcessConnectorService(liveDataProvider); + // connect the live data provider with the hovers (for data extraction and live updates) hoverProvider = createHoverHandler(projectFinder, sourceLinks, liveDataProvider); new SpringProcessLiveHoverUpdater(server, hoverProvider, projectFinder, liveDataProvider); - liveDataConnector = new SpringProcessConnectorService(); - new SpringProcessConnectorRemote(server, liveDataConnector, liveDataProvider); + // deal with locally running processes and their connections + SpringProcessConnectorLocal liveDataLocalProcessConnector = new SpringProcessConnectorLocal(liveDataService, projectObserver); + + // deal with configured remote connections + SpringProcessConnectorRemote liveDataRemoteProcessConnector = new SpringProcessConnectorRemote(server, liveDataService); + + // create and handle commands + new SpringProcessCommandHandler(server, liveDataService, liveDataLocalProcessConnector, liveDataRemoteProcessConnector); + + // track locally running processes and automatically connect to them if configured to do so + liveProcessTracker = new SpringProcessTracker(liveDataLocalProcessConnector, serverParams.watchDogInterval); - SpringProcessConnectorLocal liveHoverLocalProcessConnector = new SpringProcessConnectorLocal(liveDataConnector, liveDataProvider, projectObserver); - liveProcessTracker = new SpringProcessTracker(liveHoverLocalProcessConnector, serverParams.watchDogInterval); + // + // + // + - new SpringProcessCommandHandler(server, liveDataConnector, liveHoverLocalProcessConnector); - -// liveHoverWatchdog = new SpringLiveHoverWatchdog(server, hoverProvider, runningAppProvider, -// projectFinder, projectObserver, serverParams.watchDogInterval); -// documents.onDidChangeContent(params -> { -// TextDocument doc = params.getDocument(); -// if (getInterestingLanguages().contains(doc.getLanguageId())) { -// if (testHightlighter != null) { -// getClient() -// .highlight(new HighlightParams(params.getDocument().getId(), testHightlighter.apply(doc))); -// } else { -// try { -// liveHoverWatchdog.watchDocument(doc.getUri()); -// liveHoverWatchdog.update(doc.getUri()); -// } catch (Throwable t) { -// log.error("", t); -// } -// } -// } -// }); - -// documents.onDidClose(doc -> { -// if (testHightlighter != null) { -// getClient().highlight(new HighlightParams(doc.getId(), testHightlighter.apply(doc))); -// } else { -// liveHoverWatchdog.unwatchDocument(doc.getUri()); -// } -// }); - liveChangeDetectionWatchdog = new SpringLiveChangeDetectionWatchdog( this, server, diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessCommandHandler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessCommandHandler.java index 90e89d5f5..fbb952c87 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessCommandHandler.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessCommandHandler.java @@ -18,6 +18,7 @@ import java.util.Set; import java.util.concurrent.CompletableFuture; import org.eclipse.lsp4j.ExecuteCommandParams; +import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessConnectorRemote.RemoteBootAppData; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import com.google.gson.JsonElement; @@ -35,10 +36,13 @@ public class SpringProcessCommandHandler { private final SpringProcessConnectorService connectorService; private final SpringProcessConnectorLocal localProcessConnector; + private final SpringProcessConnectorRemote remoteProcessConnector; - public SpringProcessCommandHandler(SimpleLanguageServer server, SpringProcessConnectorService connectorService, SpringProcessConnectorLocal localProcessConnector) { + public SpringProcessCommandHandler(SimpleLanguageServer server, SpringProcessConnectorService connectorService, + SpringProcessConnectorLocal localProcessConnector, SpringProcessConnectorRemote remoteProcessConnector) { this.connectorService = connectorService; this.localProcessConnector = localProcessConnector; + this.remoteProcessConnector = remoteProcessConnector; server.onCommand(COMMAND_LIST_PROCESSES, (params) -> { return getProcessCommands(); @@ -61,6 +65,8 @@ public class SpringProcessCommandHandler { private CompletableFuture connect(ExecuteCommandParams params) { String processKey = getProcessKey(params); if (processKey != null) { + + // try local processes SpringProcessDescriptor[] processes = localProcessConnector.getProcesses(false, SpringProcessStatus.REGULAR); for (SpringProcessDescriptor process : processes) { if (process.getProcessKey().equals(processKey)) { @@ -68,6 +74,16 @@ public class SpringProcessCommandHandler { return CompletableFuture.completedFuture(null); } } + + // try remote processes + RemoteBootAppData[] remoteProcesses = remoteProcessConnector.getProcesses(); + for (RemoteBootAppData remoteProcess : remoteProcesses) { + String key = SpringProcessConnectorRemote.getProcessKey(remoteProcess); + if (processKey.equals(key)) { + remoteProcessConnector.connectProcess(remoteProcess); + return CompletableFuture.completedFuture(null); + } + } } return CompletableFuture.completedFuture(null); @@ -108,13 +124,11 @@ public class SpringProcessCommandHandler { refreshCommand.setProcessKey(processKey); result.add(refreshCommand); - if (localProcessConnector.isLocalProcess(process.getProcessKey())) { - LiveProcessCommand disconnectCommand = new LiveProcessCommand(); - disconnectCommand.setAction(COMMAND_DISCONNECT); - disconnectCommand.setLabel(label); - disconnectCommand.setProcessKey(processKey); - result.add(disconnectCommand); - } + LiveProcessCommand disconnectCommand = new LiveProcessCommand(); + disconnectCommand.setAction(COMMAND_DISCONNECT); + disconnectCommand.setLabel(label); + disconnectCommand.setProcessKey(processKey); + result.add(disconnectCommand); alreadyConnected.add(processKey); } @@ -136,6 +150,23 @@ public class SpringProcessCommandHandler { } } + // other available remote processes + RemoteBootAppData[] remoteProcesses = remoteProcessConnector.getProcesses(); + for (RemoteBootAppData remoteProcess : remoteProcesses) { + String processKey = SpringProcessConnectorRemote.getProcessKey(remoteProcess); + if (!alreadyConnected.contains(processKey)) { + String label = "remote process: " + remoteProcess.getJmxurl(); + String action = COMMAND_CONNECT; + + LiveProcessCommand command = new LiveProcessCommand(); + command.setAction(action); + command.setLabel(label); + command.setProcessKey(processKey); + + result.add(command); + } + } + return CompletableFuture.completedFuture((Object[]) result.toArray(new Object[result.size()])); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnector.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnector.java index 2d4007eb3..3806a658d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnector.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnector.java @@ -19,7 +19,7 @@ public interface SpringProcessConnector { String getLabel(); void connect() throws Exception; - void refresh() throws Exception; + SpringProcessLiveData refresh() throws Exception; void disconnect() throws Exception; void addConnectorChangeListener(SpringProcessConnectionChangeListener listener); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorLocal.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorLocal.java index 0c0f898ad..a7758fdc1 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorLocal.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorLocal.java @@ -67,14 +67,11 @@ public class SpringProcessConnectorLocal { private final Set processes; private final SpringProcessConnectorService processConnectorService; - private final SpringProcessLiveDataProvider liveDataProvider; - public SpringProcessConnectorLocal(SpringProcessConnectorService processConnector, SpringProcessLiveDataProvider liveDataProvider, - ProjectObserver projectObserver) { + public SpringProcessConnectorLocal(SpringProcessConnectorService processConnector, ProjectObserver projectObserver) { this.projects = Collections.synchronizedCollection(new HashSet<>()); this.processes = Collections.synchronizedSet(new HashSet<>()); - this.liveDataProvider = liveDataProvider; this.processConnectorService = processConnector; projectObserver.addListener(new ProjectObserver.Listener() { @@ -253,7 +250,7 @@ public class SpringProcessConnectorLocal { String urlScheme = "http"; SpringProcessConnectorOverJMX connector = new SpringProcessConnectorOverJMX( - liveDataProvider, descriptor.getProcessKey(), jmxAddress, urlScheme, processID, processName, null, null); + descriptor.getProcessKey(), jmxAddress, urlScheme, processID, processName, null, null); this.processConnectorService.connectProcess(descriptor.getProcessKey(), connector); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorOverJMX.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorOverJMX.java index bcfe83aec..a90e63933 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorOverJMX.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorOverJMX.java @@ -10,9 +10,14 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.livehover.v2; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; +import javax.management.Notification; +import javax.management.NotificationListener; +import javax.management.remote.JMXConnectionNotification; import javax.management.remote.JMXConnector; import javax.management.remote.JMXConnectorFactory; import javax.management.remote.JMXServiceURL; @@ -27,22 +32,31 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector { private static final Logger log = LoggerFactory.getLogger(SpringProcessConnectorOverJMX.class); - private final SpringProcessLiveDataProvider liveDataProvider; + private static final String JMX_CLIENT_CONNECTION_CHECK_PERIOD_PROPERTY_KEY = "jmx.remote.x.client.connection.check.period"; + private static final long JMX_HEARTBEAT_INTERVAL = 1000; + private final String processKey; private final String jmxURL; private final String urlScheme; - private final String processID; - private final String processName; - private final String host; private final String port; + // not final, might be updated with data from JMX process, if not initially set + private String processID; + private String processName; + private String host; + private final List listeners; - public SpringProcessConnectorOverJMX(SpringProcessLiveDataProvider liveDataProvider, String processKey, String jmxURL, + private JMXConnector jmxConnection; + private JMXServiceURL jmxServiceURL; + + private final NotificationListener notificationListener; + + public SpringProcessConnectorOverJMX(String processKey, String jmxURL, String urlScheme, String processID, String processName, String host, String port) { - this.liveDataProvider = liveDataProvider; this.processKey = processKey; + this.jmxURL = jmxURL; this.urlScheme = urlScheme; this.processID = processID; @@ -50,7 +64,28 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector { this.host = host; this.port = port; + this.jmxConnection = null; + this.jmxServiceURL = null; + this.listeners = new CopyOnWriteArrayList<>(); + + this.notificationListener = new NotificationListener() { + @Override + public void handleNotification(Notification notification, Object handback) { + String notificationType = notification.getType(); + + if (JMXConnectionNotification.CLOSED.equals(notificationType)) { + try { + jmxConnection.removeConnectionNotificationListener(notificationListener); + jmxConnection = null; + } + catch (Exception e) { + log.error("exception while reacting to connection close of: " + jmxURL, e); + } + announceConnectionClosed(); + } + } + }; } @Override @@ -65,40 +100,44 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector { @Override public void connect() throws Exception { + jmxServiceURL = new JMXServiceURL(jmxURL); + + Map environment = new HashMap<>(); + environment.put(JMX_CLIENT_CONNECTION_CHECK_PERIOD_PROPERTY_KEY, new Long(JMX_HEARTBEAT_INTERVAL)); + jmxConnection = JMXConnectorFactory.connect(jmxServiceURL, environment); + + jmxConnection.addConnectionNotificationListener(notificationListener, null, null); } @Override - public void refresh() throws Exception { + public SpringProcessLiveData refresh() throws Exception { log.info("try to open JMX connection to: " + jmxURL); - JMXConnector jmxConnector = null; - try { - SpringProcessLiveDataExtractorOverJMX springJMXConnector = new SpringProcessLiveDataExtractorOverJMX(); - - JMXServiceURL jmxServiceURL = new JMXServiceURL(jmxURL); - jmxConnector = JMXConnectorFactory.connect(jmxServiceURL, null); - - String hostName = host != null ? host : jmxServiceURL.getHost(); - - log.info("retrieve live data from: " + jmxURL); - SpringProcessLiveData liveData = springJMXConnector.retrieveLiveData(jmxConnector, processID, processName, urlScheme, hostName, null, port); - - if (liveData != null && liveData.getBeans() != null && !liveData.getBeans().isEmpty()) { - this.liveDataProvider.add(processKey, liveData); - return; + + if (jmxConnection != null) { + try { + SpringProcessLiveDataExtractorOverJMX springJMXConnector = new SpringProcessLiveDataExtractorOverJMX(); + + if (this.host == null) { + this.host = jmxServiceURL.getHost(); + } + + log.info("retrieve live data from: " + jmxURL); + SpringProcessLiveData liveData = springJMXConnector.retrieveLiveData(jmxConnection, processID, processName, urlScheme, host, null, port); + + if (this.processID == null) { + this.processID = liveData.getProcessID(); + } + + if (this.processName == null) { + this.processName = liveData.getProcessName(); + } + + if (liveData != null && liveData.getBeans() != null && !liveData.getBeans().isEmpty()) { + return liveData; + } } - } - catch (Exception e) { - log.error("exception while connecting to jmx: " + jmxURL, e); - } - finally { - if (jmxConnector != null) { - try { - log.info("close JMX connection to: " + jmxURL); - jmxConnector.close(); - } - catch (Exception e) { - log.error("error closing the JMX connection for: " + jmxURL, e); - } + catch (Exception e) { + log.error("exception while connecting to jmx: " + jmxURL, e); } } @@ -107,7 +146,18 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector { @Override public void disconnect() throws Exception { - this.liveDataProvider.remove(processKey); + try { + if (jmxConnection != null) { + + log.info("close JMX connection to: " + jmxURL); + jmxConnection.removeConnectionNotificationListener(notificationListener); + jmxConnection.close(); + jmxConnection = null; + } + } + catch (Exception e) { + log.error("error closing the JMX connection for: " + jmxURL, e); + } } @Override @@ -126,6 +176,4 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector { } } - - } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorRemote.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorRemote.java index 930befd8c..db926ea61 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorRemote.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorRemote.java @@ -141,12 +141,9 @@ public class SpringProcessConnectorRemote { */ private final Map remoteAppInstances; private final SpringProcessConnectorService processConnectorService; - private final SpringProcessLiveDataProvider liveDataProvider; - public SpringProcessConnectorRemote(SimpleLanguageServer server, SpringProcessConnectorService processConnector, - SpringProcessLiveDataProvider liveDataProvider) { + public SpringProcessConnectorRemote(SimpleLanguageServer server, SpringProcessConnectorService processConnector) { this.processConnectorService = processConnector; - this.liveDataProvider = liveDataProvider; this.remoteAppInstances = new HashMap<>(); server.getWorkspaceService().onDidChangeConfiguraton(this::handleSettings); @@ -181,26 +178,34 @@ public class SpringProcessConnectorRemote { for (RemoteBootAppData data : newAppData) { remoteAppInstances.computeIfAbsent(data, (_appData) -> { logger.info("Creating RemoteStringBootApp: " + _appData); - String processKey = getProcessKey(_appData); - String processID = null; - String processName = null; - String jmxURL = _appData.getJmxurl(); - String host = _appData.getHost(); - String port = _appData.getPort(); - String urlScheme = _appData.getUrlScheme(); -// boolean keepChecking = _appData.isKeepChecking(); - - SpringProcessConnectorOverJMX connector = new SpringProcessConnectorOverJMX(liveDataProvider, processKey, jmxURL, urlScheme, processID, processName, host, port); - processConnectorService.connectProcess(processKey, connector); - + connectProcess(_appData); return processKey; }); } } - private static String getProcessKey(RemoteBootAppData appData) { - return "remote"; + public static String getProcessKey(RemoteBootAppData appData) { + return "remote process - " + appData.getJmxurl(); + } + + public void connectProcess(RemoteBootAppData remoteProcess) { + String processKey = getProcessKey(remoteProcess); + String processID = null; + String processName = null; + String jmxURL = remoteProcess.getJmxurl(); + String host = remoteProcess.getHost(); + String port = remoteProcess.getPort(); + String urlScheme = remoteProcess.getUrlScheme(); +// boolean keepChecking = _appData.isKeepChecking(); + + SpringProcessConnectorOverJMX connector = new SpringProcessConnectorOverJMX(processKey, jmxURL, urlScheme, processID, processName, host, port); + processConnectorService.connectProcess(processKey, connector); + } + + public RemoteBootAppData[] getProcesses() { + Set remoteApps = this.remoteAppInstances.keySet(); + return (RemoteBootAppData[]) remoteApps.toArray(new RemoteBootAppData[remoteApps.size()]); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorService.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorService.java index cc0e8716d..2199408d1 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorService.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessConnectorService.java @@ -28,14 +28,26 @@ public class SpringProcessConnectorService { private static final int RETRY_MAX_NO = 10; private static final int RETRY_DELAY_IN_SECONDS = 3; + private final SpringProcessLiveDataProvider liveDataProvider; + private final ScheduledThreadPoolExecutor scheduler; private final ConcurrentMap connectors; private final ConcurrentMap connectedSuccess; + + private final SpringProcessConnectionChangeListener connectorListener; - public SpringProcessConnectorService() { + public SpringProcessConnectorService(SpringProcessLiveDataProvider liveDataProvider) { + this.liveDataProvider = liveDataProvider; this.scheduler = new ScheduledThreadPoolExecutor(10); this.connectors = new ConcurrentHashMap<>(); this.connectedSuccess = new ConcurrentHashMap<>(); + + this.connectorListener = new SpringProcessConnectionChangeListener() { + @Override + public void connectionClosed(String processKey) { + disconnectProcess(processKey); + } + }; } public void connectProcess(String processKey, SpringProcessConnector connector) { @@ -43,6 +55,8 @@ public class SpringProcessConnectorService { this.connectors.put(processKey, connector); this.connectedSuccess.put(processKey, false); + + connector.addConnectorChangeListener(connectorListener); try { scheduleConnect(processKey, connector, 0, TimeUnit.SECONDS, 0); @@ -64,6 +78,8 @@ public class SpringProcessConnectorService { public void disconnectProcess(String processKey) { log.info("disconnect from process: " + processKey); + this.liveDataProvider.remove(processKey); + SpringProcessConnector connector = this.connectors.remove(processKey); this.connectedSuccess.put(processKey, false); @@ -90,6 +106,7 @@ public class SpringProcessConnectorService { this.scheduler.schedule(() -> { try { connector.connect(); + refreshProcess(processKey); } catch (Exception e) { log.info("problem occured during process connect", e); @@ -98,7 +115,6 @@ public class SpringProcessConnectorService { scheduleConnect(processKey, connector, RETRY_DELAY_IN_SECONDS, TimeUnit.SECONDS, retryNo + 1); } } - refreshProcess(processKey); }, delay, unit); } @@ -124,14 +140,20 @@ public class SpringProcessConnectorService { this.scheduler.schedule(() -> { try { - connector.refresh(); - this.connectedSuccess.put(processKey, true); + SpringProcessLiveData newLiveData = connector.refresh(); + if (newLiveData != null) { + if (!this.liveDataProvider.add(processKey, newLiveData)) { + this.liveDataProvider.update(processKey, newLiveData); + } + + this.connectedSuccess.put(processKey, true); + } } catch (Exception e) { log.info("problem occured during process live data refresh", e); if (retryNo < RETRY_MAX_NO) { - scheduleRefresh(processKey, connector, 3, TimeUnit.SECONDS, retryNo + 1); + scheduleRefresh(processKey, connector, RETRY_DELAY_IN_SECONDS, TimeUnit.SECONDS, retryNo + 1); } else { disconnectProcess(processKey); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveHoverUpdater.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveHoverUpdater.java index 455a3c197..350008eb1 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveHoverUpdater.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/livehover/v2/SpringProcessLiveHoverUpdater.java @@ -12,6 +12,7 @@ package org.springframework.ide.vscode.boot.java.livehover.v2; import java.util.Arrays; import java.util.Map; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicReference; @@ -68,7 +69,9 @@ public class SpringProcessLiveHoverUpdater { }); liveDataProvider.addLiveDataChangeListener(event -> { - update(); + CompletableFuture.runAsync(() -> { + update(); + }); }); }