Refine api for connected process listeners

Events now include some extra information instead of just processKey.
This commit is contained in:
Kris De Volder
2022-05-09 16:12:59 -07:00
parent 36f7198082
commit 006cbd0721
18 changed files with 161 additions and 48 deletions

View File

@@ -0,0 +1,42 @@
package org.springframework.ide.vscode.commons.protocol;
/**
* Json data object that is sent by process connected/disconnect/updated events. The main purpose
* is to identify a process to an external client such as (real use case!) vscode-boot-dashboard
* from Microsoft; or other 3rd party vscode-extension that want to integrate with
* the live process connections tracked by vscode-spring-boot.
*
* @author Kris De Volder
*/
public class LiveProcessSummary {
private String type;
private String processKey;
private String processName;
private String pid; //only meaningful for type = 'local'
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getProcessKey() {
return processKey;
}
public void setProcessKey(String processKey) {
this.processKey = processKey;
}
public String getProcessName() {
return processName;
}
public void setProcessName(String processName) {
this.processName = processName;
}
public String getPid() {
return pid;
}
public void setPid(String pid) {
this.pid = pid;
}
}

View File

@@ -38,13 +38,13 @@ public interface STS4LanguageClient extends LanguageClient {
@JsonNotification("sts/liveprocess/connected")
void liveProcessConnected(String processKey);
void liveProcessConnected(LiveProcessSummary processKey);
@JsonNotification("sts/liveprocess/disconnected")
void liveProcessDisconnected(String processKey);
void liveProcessDisconnected(LiveProcessSummary processKey);
@JsonNotification("sts/liveprocess/updated")
void liveProcessDataUpdated(String processKey);
void liveProcessDataUpdated(LiveProcessSummary processKey);
@JsonNotification("sts/highlight")
void highlight(HighlightParams highlights);

View File

@@ -114,6 +114,7 @@ import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguage
import org.springframework.ide.vscode.commons.languageserver.util.SimpleWorkspaceService;
import org.springframework.ide.vscode.commons.protocol.CursorMovement;
import org.springframework.ide.vscode.commons.protocol.HighlightParams;
import org.springframework.ide.vscode.commons.protocol.LiveProcessSummary;
import org.springframework.ide.vscode.commons.protocol.ProgressParams;
import org.springframework.ide.vscode.commons.protocol.STS4LanguageClient;
import org.springframework.ide.vscode.commons.protocol.java.ClasspathListenerParams;
@@ -406,15 +407,15 @@ public class LanguageServerHarness {
}
@Override
public void liveProcessConnected(String processKey) {
public void liveProcessConnected(LiveProcessSummary process) {
}
@Override
public void liveProcessDisconnected(String processKey) {
public void liveProcessDisconnected(LiveProcessSummary process) {
}
@Override
public void liveProcessDataUpdated(String processKey) {
public void liveProcessDataUpdated(LiveProcessSummary process) {
}
});

View File

@@ -0,0 +1,10 @@
package org.springframework.ide.vscode.boot.java.livehover.v2;
public enum ProcessType {
LOCAL,
REMOTE;
String jsonName() {
return name().toLowerCase();
}
}

View File

@@ -25,6 +25,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessConnectorRemote.RemoteBootAppData;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.protocol.LiveProcessSummary;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -74,15 +75,20 @@ public class SpringProcessCommandHandler {
log.info("Registered command handler: {}",COMMAND_DISCONNECT);
server.onCommand(COMMAND_GET, (params) -> {
return get(params);
return handleLiveProcessRequest(params);
});
log.info("Registered command handler: {}",COMMAND_GET);
server.onCommand(COMMAND_LIST_CONNECTED, (params) -> {
return CompletableFuture.completedFuture(Stream.of(connectorService.getConnectedProcesses())
.map(process -> process.getProcessKey())
.collect(Collectors.toList())
);
List<LiveProcessSummary> result = new ArrayList<>();
for (SpringProcessConnector process : connectorService.getConnectedProcesses()) {
String processKey = process.getProcessKey();
SpringProcessLiveData liveData = connectorService.getLiveData(processKey);
if (liveData!=null) {
result.add(SpringProcessLiveDataProvider.createProcessSummary(processKey, liveData));
}
}
return CompletableFuture.completedFuture(result);
});
}
@@ -224,7 +230,7 @@ public class SpringProcessCommandHandler {
return null;
}
private CompletableFuture<Object> get(ExecuteCommandParams params) {
private CompletableFuture<Object> handleLiveProcessRequest(ExecuteCommandParams params) {
String processKey = getProcessKey(params);
String endpoint = getArgumentByKey(params, "endpoint");
if (processKey != null) {

View File

@@ -15,6 +15,7 @@ package org.springframework.ide.vscode.boot.java.livehover.v2;
*/
public interface SpringProcessConnector {
ProcessType getProcessType();
String getProcessKey();
void connect() throws Exception;

View File

@@ -213,7 +213,7 @@ public class SpringProcessConnectorLocal {
String processName = getProcessName(vmDescriptor);
String urlScheme = "http";
SpringProcessConnectorOverJMX connector = new SpringProcessConnectorOverJMX(
SpringProcessConnectorOverJMX connector = new SpringProcessConnectorOverJMX(ProcessType.LOCAL,
descriptor.getProcessKey(), jmxAddress, urlScheme, processID, processName, descriptor.getProjectName(), null, null);
this.processConnectorService.connectProcess(descriptor.getProcessKey(), connector);

View File

@@ -2,6 +2,7 @@ package org.springframework.ide.vscode.boot.java.livehover.v2;
public class SpringProcessConnectorOverHttp implements SpringProcessConnector {
private final ProcessType processType;
private final String processKey;
private final String actuatorUrl;
private final String urlScheme;
@@ -15,10 +16,10 @@ public class SpringProcessConnectorOverHttp implements SpringProcessConnector {
private HttpActuatorConnection actuatorConnection;
public SpringProcessConnectorOverHttp(String processKey, String actuatorUrl,
public SpringProcessConnectorOverHttp(ProcessType processType, String processKey, String actuatorUrl,
String urlScheme, String processID, String processName, String projectName, String host, String port) {
this.processType = processType;
this.processKey = processKey;
this.actuatorUrl = actuatorUrl;
@@ -29,7 +30,13 @@ public class SpringProcessConnectorOverHttp implements SpringProcessConnector {
this.host = host;
this.port = port;
}
@Override
public ProcessType getProcessType() {
return processType;
}
@Override
public String getProcessKey() {
return processKey;
@@ -43,7 +50,7 @@ public class SpringProcessConnectorOverHttp implements SpringProcessConnector {
@Override
public SpringProcessLiveData refresh(SpringProcessLiveData currentData) throws Exception {
if (actuatorConnection != null) {
SpringProcessLiveData liveData = new SpringProcessLiveDataExtractorOverHttp().retrieveLiveData(actuatorConnection, processID, processName, urlScheme, host, null, port, currentData);
SpringProcessLiveData liveData = new SpringProcessLiveDataExtractorOverHttp().retrieveLiveData(getProcessType(), actuatorConnection, processID, processName, urlScheme, host, null, port, currentData);
if (this.processID == null) {
this.processID = liveData.getProcessID();

View File

@@ -53,9 +53,11 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector {
private final NotificationListener notificationListener;
public SpringProcessConnectorOverJMX(String processKey, String jmxURL,
String urlScheme, String processID, String processName, String projectName, String host, String port) {
private final ProcessType processType;
public SpringProcessConnectorOverJMX(ProcessType processType, String processKey, String jmxURL,
String urlScheme, String processID, String processName, String projectName, String host, String port) {
this.processType = processType;
this.processKey = processKey;
this.jmxURL = jmxURL;
@@ -90,6 +92,11 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector {
};
}
@Override
public ProcessType getProcessType() {
return processType;
}
@Override
public String getProcessKey() {
return processKey;
@@ -119,7 +126,7 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector {
}
log.info("retrieve live data from: " + jmxURL);
SpringProcessLiveData liveData = springJMXConnector.retrieveLiveData(jmxConnection, processID, processName, urlScheme, host, null, port, currentData);
SpringProcessLiveData liveData = springJMXConnector.retrieveLiveData(getProcessType(), jmxConnection, processID, processName, urlScheme, host, null, port, currentData);
if (this.processID == null) {
this.processID = liveData.getProcessID();
@@ -194,5 +201,5 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector {
+ processName + ", listeners=" + listeners + ", jmxConnection=" + jmxConnection + ", jmxServiceURL="
+ jmxServiceURL + ", notificationListener=" + notificationListener + "]";
}
}

View File

@@ -245,10 +245,10 @@ public class SpringProcessConnectorRemote {
// boolean keepChecking = _appData.isKeepChecking();
if (jmxURL.startsWith("http")) {
SpringProcessConnectorOverHttp connector = new SpringProcessConnectorOverHttp(processKey, jmxURL, urlScheme, processID, processName, urlScheme, host, port);
SpringProcessConnectorOverHttp connector = new SpringProcessConnectorOverHttp(ProcessType.REMOTE, processKey, jmxURL, urlScheme, processID, processName, urlScheme, host, port);
processConnectorService.connectProcess(processKey, connector);
} else {
SpringProcessConnectorOverJMX connector = new SpringProcessConnectorOverJMX(processKey, jmxURL, urlScheme, processID, processName, null, host, port);
SpringProcessConnectorOverJMX connector = new SpringProcessConnectorOverJMX(ProcessType.REMOTE, processKey, jmxURL, urlScheme, processID, processName, null, host, port);
processConnectorService.connectProcess(processKey, connector);
}
}

View File

@@ -15,6 +15,7 @@ package org.springframework.ide.vscode.boot.java.livehover.v2;
*/
public class SpringProcessLiveData {
private final ProcessType processType;
private final String processName;
private final String processID;
@@ -31,11 +32,12 @@ public class SpringProcessLiveData {
private final LiveMetricsModel metrics;
private StartupMetricsModel startup;
public SpringProcessLiveData(String processName, String processID, String contextPath, String urlScheme,
public SpringProcessLiveData(ProcessType processType, String processName, String processID, String contextPath, String urlScheme,
String port, String host, LiveBeansModel beansModel, String[] activeProfiles,
LiveRequestMapping[] requestMappings, LiveConditional[] conditionals, LiveProperties properties,
LiveMetricsModel metrics, StartupMetricsModel startup) {
super();
this.processType = processType;
this.processName = processName;
this.processID = processID;
this.contextPath = contextPath;
@@ -51,6 +53,10 @@ public class SpringProcessLiveData {
this.startup = startup;
}
public ProcessType getProcessType() {
return processType;
}
public String getProcessName() {
return this.processName;

View File

@@ -40,6 +40,7 @@ public class SpringProcessLiveDataExtractorOverHttp {
.create();
/**
* @param processType distinguish different types of processes (i.e. local vs remote)
* @param processID if null, will be determined searching existing mbeans for that information (for remote processes via platform beans runtime name)
* @param processName if null, will be determined searching existing mbeans for that information (for remote processes infering the java command from the system properties)
* @param urlScheme should always be != null
@@ -48,7 +49,7 @@ public class SpringProcessLiveDataExtractorOverHttp {
* @param port if null, will be determined searching existing mbeans for that information (for local processes)
* @param currentData currently stored live data
*/
public SpringProcessLiveData retrieveLiveData(ActuatorConnection connection, String processID, String processName,
public SpringProcessLiveData retrieveLiveData(ProcessType processType, ActuatorConnection connection, String processID, String processName,
String urlScheme, String host, String contextPath, String port, SpringProcessLiveData currentData) {
try {
@@ -85,6 +86,7 @@ public class SpringProcessLiveDataExtractorOverHttp {
// }
return new SpringProcessLiveData(
processType,
processName,
processID,
contextPath,

View File

@@ -56,6 +56,7 @@ public class SpringProcessLiveDataExtractorOverJMX {
.create();
/**
* @param processType
* @param processID if null, will be determined searching existing mbeans for that information (for remote processes via platform beans runtime name)
* @param processName if null, will be determined searching existing mbeans for that information (for remote processes infering the java command from the system properties)
* @param urlScheme should always be != null
@@ -64,7 +65,7 @@ public class SpringProcessLiveDataExtractorOverJMX {
* @param port if null, will be determined searching existing mbeans for that information (for local processes)
* @param currentData currently stored live data
*/
public SpringProcessLiveData retrieveLiveData(JMXConnector jmxConnector, String processID, String processName,
public SpringProcessLiveData retrieveLiveData(ProcessType processType, JMXConnector jmxConnector, String processID, String processName,
String urlScheme, String host, String contextPath, String port, SpringProcessLiveData currentData) {
try {
@@ -104,6 +105,7 @@ public class SpringProcessLiveDataExtractorOverJMX {
}
return new SpringProcessLiveData(
processType,
processName,
processID,
contextPath,

View File

@@ -17,6 +17,7 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.protocol.LiveProcessSummary;
import org.springframework.ide.vscode.commons.protocol.STS4LanguageClient;
import org.springframework.ide.vscode.commons.util.Assert;
@@ -48,7 +49,7 @@ public class SpringProcessLiveDataProvider {
SpringProcessLiveData oldData = this.liveData.putIfAbsent(processKey, liveData);
if (oldData == null) {
announceChangedLiveData();
getClient().liveProcessConnected(processKey);
getClient().liveProcessConnected(createProcessSummary(processKey, liveData));
}
return oldData == null;
}
@@ -63,17 +64,16 @@ public class SpringProcessLiveDataProvider {
SpringProcessLiveData removed = this.liveData.remove(processKey);
if (removed != null) {
announceChangedLiveData();
getClient().liveProcessDisconnected(processKey);
getClient().liveProcessDisconnected(createProcessSummary(processKey, removed));
}
}
public void update(String processKey, SpringProcessLiveData liveData) {
this.liveData.put(processKey, liveData);
announceChangedLiveData();
getClient().liveProcessDataUpdated(processKey);
getClient().liveProcessDataUpdated(createProcessSummary(processKey, liveData));
}
public void addLiveDataChangeListener(SpringProcessLiveDataChangeListener listener) {
this.listeners.add(listener);
}
@@ -90,8 +90,17 @@ public class SpringProcessLiveDataProvider {
}
}
SpringProcessLiveData getCurrent(String processKey) {
public SpringProcessLiveData getCurrent(String processKey) {
return this.liveData.get(processKey);
}
public static LiveProcessSummary createProcessSummary(String processKey, SpringProcessLiveData liveData) {
LiveProcessSummary p = new LiveProcessSummary();
p.setType(liveData.getProcessType().jsonName());
p.setProcessKey(processKey);
p.setProcessName(liveData.getProcessName());
p.setPid(liveData.getProcessID());
return p;
}
}

View File

@@ -23,6 +23,7 @@ import org.springframework.ide.vscode.boot.java.livehover.v2.LiveMetricsModel;
import org.springframework.ide.vscode.boot.java.livehover.v2.LiveProperties;
import org.springframework.ide.vscode.boot.java.livehover.v2.LiveRequestMapping;
import org.springframework.ide.vscode.boot.java.livehover.v2.LiveRequestMappingBoot1xRequestMapping;
import org.springframework.ide.vscode.boot.java.livehover.v2.ProcessType;
import org.springframework.ide.vscode.boot.java.livehover.v2.StartupMetricsModel;
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveData;
@@ -137,7 +138,7 @@ public class SpringProcessLiveDataBuilder {
}
public SpringProcessLiveData build() {
return new SpringProcessLiveData(processName, processID, contextPath, urlScheme, port, host, beansModel, activeProfiles, requestMappings, conditionals, properties, metrics, startup);
return new SpringProcessLiveData(ProcessType.LOCAL, processName, processID, contextPath, urlScheme, port, host, beansModel, activeProfiles, requestMappings, conditionals, properties, metrics, startup);
}
}

View File

@@ -1,5 +1,6 @@
import { Event } from "vscode";
import { LanguageClient } from "vscode-languageclient/node";
import { LiveProcess } from "./notification";
export interface ExtensionAPI {
readonly client: LanguageClient;
@@ -7,17 +8,17 @@ export interface ExtensionAPI {
/**
* An event which fires on live process is connected. Payload is processKey.
*/
readonly onDidLiveProcessConnect: Event<string>
readonly onDidLiveProcessConnect: Event<LiveProcess>
/**
* An event which fires on live process is disconnected. Payload is processKey.
*/
readonly onDidLiveProcessDisconnect: Event<string>
readonly onDidLiveProcessDisconnect: Event<LiveProcess>
/**
* An event which fires on live process data change. Payload is processKey.
*/
readonly onDidLiveProcessUpdate: Event<string>
readonly onDidLiveProcessUpdate: Event<LiveProcess>
/**
* A command to get live process data.
@@ -29,7 +30,7 @@ export interface ExtensionAPI {
*
* Returns a list of processKeys.
*/
readonly listConnectedProcesses: () => Promise<string[]>
readonly listConnectedProcesses: () => Promise<LiveProcess[]>
}
interface LiveProcessDataQuery {

View File

@@ -1,13 +1,13 @@
import { commands, Uri } from "vscode";
import { Emitter, LanguageClient } from "vscode-languageclient/node";
import { ExtensionAPI } from "./api";
import { LiveProcessConnectedNotification, LiveProcessDisconnectedNotification, LiveProcessUpdatedNotification } from "./notification";
import { LiveProcess, LiveProcessConnectedNotification, LiveProcessDisconnectedNotification, LiveProcessUpdatedNotification } from "./notification";
export class ApiManager {
public api: ExtensionAPI;
private onDidLiveProcessConnectEmitter: Emitter<string> = new Emitter<string>();
private onDidLiveProcessDisconnectEmitter: Emitter<string> = new Emitter<string>();
private onDidLiveProcessUpdateEmitter: Emitter<string> = new Emitter<string>();
private onDidLiveProcessConnectEmitter: Emitter<LiveProcess> = new Emitter<LiveProcess>();
private onDidLiveProcessDisconnectEmitter: Emitter<LiveProcess> = new Emitter<LiveProcess>();
private onDidLiveProcessUpdateEmitter: Emitter<LiveProcess> = new Emitter<LiveProcess>();
public constructor(client: LanguageClient) {
const onDidLiveProcessConnect = this.onDidLiveProcessConnectEmitter.event;
@@ -20,13 +20,13 @@ export class ApiManager {
}
const COMMAND_LIVEDATA_LIST_CONNECTED = "sts/livedata/listConnected"
const listConnectedProcesses = async () : Promise<string[]> => {
const listConnectedProcesses = async () : Promise<LiveProcess[]> => {
return await commands.executeCommand(COMMAND_LIVEDATA_LIST_CONNECTED);
}
client.onNotification(LiveProcessConnectedNotification.type, (processKey: string) => this.onDidLiveProcessConnectEmitter.fire(processKey));
client.onNotification(LiveProcessDisconnectedNotification.type, (processKey: string) => this.onDidLiveProcessDisconnectEmitter.fire(processKey));
client.onNotification(LiveProcessUpdatedNotification.type, (processKey: string) => this.onDidLiveProcessUpdateEmitter.fire(processKey));
client.onNotification(LiveProcessConnectedNotification.type, (process: LiveProcess) => this.onDidLiveProcessConnectEmitter.fire(process));
client.onNotification(LiveProcessDisconnectedNotification.type, (process: LiveProcess) => this.onDidLiveProcessDisconnectEmitter.fire(process));
client.onNotification(LiveProcessUpdatedNotification.type, (process: LiveProcess) => this.onDidLiveProcessUpdateEmitter.fire(process));
this.api = {
client,

View File

@@ -1,13 +1,31 @@
import { NotificationType } from "vscode-languageclient";
/**
* Common information provided by all live process notifications, for all types
* of events and for all types of processes.
*/
export interface LiveProcess {
type: string;
processKey: string;
processName: string;
}
/**
* Specialized interface for type 'local' LiveProcess.
*/
export interface LocalLiveProcess extends LiveProcess {
type: "local"
pid: string
}
export namespace LiveProcessConnectedNotification {
export const type = new NotificationType<string>('sts/liveprocess/connected');
export const type = new NotificationType<LiveProcess>('sts/liveprocess/connected');
}
export namespace LiveProcessDisconnectedNotification {
export const type = new NotificationType<string>('sts/liveprocess/disconnected');
export const type = new NotificationType<LiveProcess>('sts/liveprocess/disconnected');
}
export namespace LiveProcessUpdatedNotification {
export const type = new NotificationType<string>('sts/liveprocess/updated');
export const type = new NotificationType<LiveProcess>('sts/liveprocess/updated');
}