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 76b60bbf39..f56521b4c2 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 @@ -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; @@ -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 connect(ExecuteCommandParams params) { @@ -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 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(); } @@ -192,5 +202,42 @@ else if (arg instanceof JsonObject) { return null; } + + private CompletableFuture get(ExecuteCommandParams params) { + String processKey = getProcessKey(params); + 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); + } } 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 9fccf5e229..f2ff2d7b37 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 @@ -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); diff --git a/vscode-extensions/vscode-spring-boot/lib/Main.ts b/vscode-extensions/vscode-spring-boot/lib/Main.ts index 700a90b6a9..a056086a94 100644 --- a/vscode-extensions/vscode-spring-boot/lib/Main.ts +++ b/vscode-extensions/vscode-spring-boot/lib/Main.ts @@ -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"; @@ -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 { +export function activate(context: VSCode.ExtensionContext): Thenable { // registerPipelineGenerator(context); let options : commons.ActivatorOptions = { @@ -109,6 +111,6 @@ export function activate(context: VSCode.ExtensionContext): Thenable { liveHoverUi.activate(client, options, context); - return client; + return new ApiManager(client).api; }); } diff --git a/vscode-extensions/vscode-spring-boot/lib/api.d.ts b/vscode-extensions/vscode-spring-boot/lib/api.d.ts new file mode 100644 index 0000000000..92736a926f --- /dev/null +++ b/vscode-extensions/vscode-spring-boot/lib/api.d.ts @@ -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 + + /** + * An event which fires on live process is disconnected. Payload is processKey. + */ + readonly onDidLiveProcessDisconnect: Event + + /** + * A command to get live process data. + */ + readonly getLiveProcessData: (query: SimpleQuery | BeansQuery) => Promise + +} + +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; +} diff --git a/vscode-extensions/vscode-spring-boot/lib/apiManager.ts b/vscode-extensions/vscode-spring-boot/lib/apiManager.ts new file mode 100644 index 0000000000..ed576c3f33 --- /dev/null +++ b/vscode-extensions/vscode-spring-boot/lib/apiManager.ts @@ -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 = new Emitter(); + private onDidLiveProcessDisconnectEmitter: Emitter = new Emitter(); + + 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. + 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 + }; + } +} diff --git a/vscode-extensions/vscode-spring-boot/lib/notification.ts b/vscode-extensions/vscode-spring-boot/lib/notification.ts new file mode 100644 index 0000000000..e553be7b1d --- /dev/null +++ b/vscode-extensions/vscode-spring-boot/lib/notification.ts @@ -0,0 +1,9 @@ +import { NotificationType } from "vscode-languageclient"; + +export namespace LiveProcessConnectedNotification { + export const type = new NotificationType('sts/liveprocess/connected'); +} + +export namespace LiveProcessDisconnectedNotification { + export const type = new NotificationType('sts/liveprocess/disconnected'); +}