Skip to content

add extension APIs to get live data #751

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class SpringProcessCommandHandler {
private static final String COMMAND_CONNECT = "sts/livedata/connect";
private static final String COMMAND_REFRESH = "sts/livedata/refresh";
private static final String COMMAND_DISCONNECT = "sts/livedata/disconnect";
private static final String COMMAND_GET = "sts/livedata/get";

private final SpringProcessConnectorService connectorService;
private final SpringProcessConnectorLocal localProcessConnector;
Expand Down Expand Up @@ -68,6 +69,11 @@ public SpringProcessCommandHandler(SimpleLanguageServer server, SpringProcessCon
return disconnect(params);
});
log.info("Registered command handler: {}",COMMAND_DISCONNECT);

server.onCommand(COMMAND_GET, (params) -> {
return get(params);
});
log.info("Registered command handler: {}",COMMAND_GET);
}

private CompletableFuture<Object> connect(ExecuteCommandParams params) {
Expand Down Expand Up @@ -174,16 +180,20 @@ private String createLabel(String processId, String processName) {
}

private String getProcessKey(ExecuteCommandParams params) {
return getArgumentByKey(params, "processKey");
}

private String getArgumentByKey(ExecuteCommandParams params, String name) {
List<Object> arguments = params.getArguments();
for (Object arg : arguments) {
if (arg instanceof Map<?, ?>) {
Object value = ((Map<?, ?>) arg).get("processKey");
Object value = ((Map<?, ?>) arg).get(name);
if (value != null) {
return value.toString();
}
}
else if (arg instanceof JsonObject) {
JsonElement element = ((JsonObject) arg).get("processKey");
JsonElement element = ((JsonObject) arg).get(name);
if (element != null) {
return element.getAsString();
}
Expand All @@ -192,5 +202,42 @@ else if (arg instanceof JsonObject) {

return null;
}

private CompletableFuture<Object> get(ExecuteCommandParams params) {
String processKey = getProcessKey(params);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't getProcessKey() be removed completely in favour of getArgumentByKey(String key)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think getProcessKey() is just a convenience method, I don't mind having such a method. But I also don't mind if we removed getProcessKey() and instead called getArgumentByKey(params, "processKey").

String dataKey = getArgumentByKey(params, "dataKey");
if (processKey != null) {
SpringProcessLiveData data = connectorService.getLiveData(processKey);
switch(dataKey) {
case "properties": {
return CompletableFuture.completedFuture(data.getLiveProperties());
}
case "beans": {
String beanName = getArgumentByKey(params, "beanName");
if (beanName != null) {
return CompletableFuture.completedFuture(data.getBeans().getBeansOfName(beanName));
}
String dependingOn = getArgumentByKey(params, "dependingOn");
if (dependingOn != null) {
return CompletableFuture.completedFuture(data.getBeans().getBeansDependingOn(dependingOn));
}
return CompletableFuture.completedFuture(data.getBeans().getBeanNames());

}
case "mappings": {
return CompletableFuture.completedFuture(data.getRequestMappings());
}
case "contextPath": {
return CompletableFuture.completedFuture(data.getContextPath());
}
case "port": {
return CompletableFuture.completedFuture(data.getPort());
}
default: {}
}
}

return CompletableFuture.completedFuture(null);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ public void refreshProcess(String processKey) {
}
}

public SpringProcessLiveData getLiveData(String processKey) {
return this.liveDataProvider.getCurrent(processKey);
}

public void disconnectProcess(String processKey) {
log.info("disconnect from process: " + processKey);

Expand Down
6 changes: 4 additions & 2 deletions vscode-extensions/vscode-spring-boot/lib/Main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import * as liveHoverUi from './live-hover-connect-ui';

import {LanguageClient} from "vscode-languageclient/node";
import { startDebugSupport } from './debug-config-provider';
import { ApiManager } from "./apiManager";
import { ExtensionAPI } from "./api";

const PROPERTIES_LANGUAGE_ID = "spring-boot-properties";
const YAML_LANGUAGE_ID = "spring-boot-properties-yaml";
Expand All @@ -18,7 +20,7 @@ const XML_LANGUAGE_ID = "xml";
const NEVER_SHOW_AGAIN = "Do not show again";

/** Called when extension is activated */
export function activate(context: VSCode.ExtensionContext): Thenable<LanguageClient> {
export function activate(context: VSCode.ExtensionContext): Thenable<ExtensionAPI> {

// registerPipelineGenerator(context);
let options : commons.ActivatorOptions = {
Expand Down Expand Up @@ -109,6 +111,6 @@ export function activate(context: VSCode.ExtensionContext): Thenable<LanguageCli

return commons.activate(options, context).then(client => {
liveHoverUi.activate(client, options, context);
return client;
return new ApiManager(client).api;
});
}
42 changes: 42 additions & 0 deletions vscode-extensions/vscode-spring-boot/lib/api.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Event } from "vscode";
import { LanguageClient } from "vscode-languageclient/node";

export interface ExtensionAPI {
readonly client: LanguageClient;

/**
* An event which fires on live process is connected. Payload is processKey.
*/
readonly onDidLiveProcessConnect: Event<string>

/**
* An event which fires on live process is disconnected. Payload is processKey.
*/
readonly onDidLiveProcessDisconnect: Event<string>

/**
* A command to get live process data.
*/
readonly getLiveProcessData: (query: SimpleQuery | BeansQuery) => Promise<any>

}

interface LiveProcessDataQuery {
/**
* unique identifier of a connected live process.
*/
processKey: string;
}

interface SimpleQuery extends LiveProcessDataQuery {
endpoint: "mappings" | "contextPath" | "port" | "properties";
}

interface BeansQuery extends LiveProcessDataQuery {
endpoint: "beans";
/**
* if provided, return corresponding beans via name.
*/
beanName?: string;
dependingOn?: string;
}
31 changes: 31 additions & 0 deletions vscode-extensions/vscode-spring-boot/lib/apiManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { commands, Uri } from "vscode";
import { Emitter, LanguageClient } from "vscode-languageclient/node";
import { ExtensionAPI } from "./api";
import { LiveProcessConnectedNotification, LiveProcessDisconnectedNotification } from "./notification";

export class ApiManager {
public api: ExtensionAPI;
private onDidLiveProcessConnectEmitter: Emitter<string> = new Emitter<string>();
private onDidLiveProcessDisconnectEmitter: Emitter<string> = new Emitter<string>();

public constructor(private client: LanguageClient) {
const onDidLiveProcessConnect = this.onDidLiveProcessConnectEmitter.event;
const onDidLiveProcessDisconnect = this.onDidLiveProcessDisconnectEmitter.event;

const COMMAND_LIVEDATA_GET = "sts/livedata/get";
const getLiveProcessData = async (query) => {
await commands.executeCommand(COMMAND_LIVEDATA_GET, query);
}

// TODO: STS server should send corresponding notification back.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corresponding logic has not been implemented in spring-boot-language-server yet. As this project is well decoupled, I'm not sure where should be the best place to do that. The remaning work should be:

  • define custom notifications in STS4LanguageClient
  • send them at the place whereever live process connection is established/disconnected.

Copy link
Contributor

@BoykoAlex BoykoAlex Apr 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessConnectorService is the place to fire off connect/disconnect events.

(For example line 155 would be the place to send connect notification. Note SimpleLanguageServer is in the constructor, store server.getClient() in the field to call notifyConnected(...). Add notifyConnected, notifyDisconnected to the STS4LangugeClient etc. - let us know if you get lost with this, will be happy to help further)
@kdvolder please let us know if you had something else in mind for these notifications

client.onNotification(LiveProcessConnectedNotification.type, (processKey: string) => this.onDidLiveProcessConnectEmitter.fire(processKey));
client.onNotification(LiveProcessDisconnectedNotification.type, (processKey: string) => this.onDidLiveProcessDisconnectEmitter.fire(processKey));

this.api = {
client,
onDidLiveProcessConnect,
onDidLiveProcessDisconnect,
getLiveProcessData
};
}
}
9 changes: 9 additions & 0 deletions vscode-extensions/vscode-spring-boot/lib/notification.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { NotificationType } from "vscode-languageclient";

export namespace LiveProcessConnectedNotification {
export const type = new NotificationType<string>('sts/liveprocess/connected');
}

export namespace LiveProcessDisconnectedNotification {
export const type = new NotificationType<string>('sts/liveprocess/disconnected');
}