Add/Connect remote process command
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018, 2021 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2022 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -11,7 +11,12 @@
|
||||
package org.springframework.ide.vscode.boot.app;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
@@ -34,6 +39,9 @@ import org.springframework.ide.vscode.boot.java.links.JavaServerElementLocationP
|
||||
import org.springframework.ide.vscode.boot.java.links.JdtJavaDocumentUriProvider;
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory;
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessConnectorRemote;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessConnectorRemote.RemoteBootAppData;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessConnectorService;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.v2.SpringProcessLiveDataProvider;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SymbolCache;
|
||||
@@ -77,6 +85,8 @@ import org.springframework.ide.vscode.languageserver.starter.LanguageServerRunne
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonElement;
|
||||
|
||||
import reactor.core.publisher.Hooks;
|
||||
|
||||
@@ -118,7 +128,53 @@ public class BootLanguageServerBootApp {
|
||||
SpringProcessLiveDataProvider liveDataProvider() {
|
||||
return new SpringProcessLiveDataProvider();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SpringProcessConnectorService processConnectorService(SimpleLanguageServer server, SpringProcessLiveDataProvider liveDataProvider) {
|
||||
return new SpringProcessConnectorService(server, liveDataProvider);
|
||||
}
|
||||
|
||||
@Bean
|
||||
SpringProcessConnectorRemote remoteAppsFromSettingsConnector(SimpleLanguageServer server, SpringProcessConnectorService liveDataService) {
|
||||
SpringProcessConnectorRemote bean = new SpringProcessConnectorRemote(server, liveDataService);
|
||||
server.getWorkspaceService().onDidChangeConfiguraton(settings -> {
|
||||
RemoteBootAppData[] appData = settings.getAs(RemoteBootAppData[].class, "boot-java", "remote-apps");
|
||||
if (appData == null) {
|
||||
//Avoid NPE
|
||||
appData = new RemoteBootAppData[0];
|
||||
}
|
||||
bean.updateApps(appData);
|
||||
});
|
||||
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
SpringProcessConnectorRemote remoteAppsFromCommandsConnector(SimpleLanguageServer server, SpringProcessConnectorService liveDataService) {
|
||||
SpringProcessConnectorRemote bean = new SpringProcessConnectorRemote(server, liveDataService);
|
||||
final Map<String, RemoteBootAppData[]> allRemoteApps = new HashMap<>();
|
||||
final Gson gson = new Gson();
|
||||
server.onCommand("sts/livedata/remoteConnect", params -> {
|
||||
List<Object> args = params.getArguments();
|
||||
String owner = ((JsonElement) args.get(0)).getAsString();
|
||||
RemoteBootAppData[] data = gson.fromJson((JsonElement) args.get(1), RemoteBootAppData[].class);
|
||||
if (data.length > 0) {
|
||||
allRemoteApps.put(owner, data);
|
||||
} else {
|
||||
allRemoteApps.remove(owner);
|
||||
}
|
||||
List<RemoteBootAppData> all = new ArrayList<>();
|
||||
for (RemoteBootAppData[] remoteBootAppData : allRemoteApps.values()) {
|
||||
for (RemoteBootAppData r : remoteBootAppData) {
|
||||
all.add(r);
|
||||
}
|
||||
}
|
||||
bean.updateApps(all.toArray(new RemoteBootAppData[all.size()]));
|
||||
return CompletableFuture.completedFuture(null);
|
||||
});
|
||||
return bean;
|
||||
}
|
||||
|
||||
@ConditionalOnMissingClass("org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness")
|
||||
@Bean AdHocSpringPropertyIndexProvider adHocProperties(BootLanguageServerParams params, FileObserver fileObserver, DocumentEventListenerManager documentEvents) {
|
||||
return new AdHocSpringPropertyIndexProvider(params.projectFinder, params.projectObserver, fileObserver, documentEvents);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018, 2021 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2022 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -11,7 +11,6 @@
|
||||
package org.springframework.ide.vscode.boot.app;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.lsp4j.MessageType;
|
||||
import org.slf4j.Logger;
|
||||
@@ -19,6 +18,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents;
|
||||
import org.springframework.ide.vscode.boot.java.links.JavaElementLocationProvider;
|
||||
import org.springframework.ide.vscode.boot.java.links.SourceLinks;
|
||||
@@ -62,6 +62,7 @@ public class BootLanguageServerInitializer implements InitializingBean {
|
||||
@Autowired YamlAssistContextProvider yamlAssistContextProvider;
|
||||
@Autowired SymbolCache symbolCache;
|
||||
@Autowired SpringProcessLiveDataProvider liveDataProvider;
|
||||
@Autowired ApplicationContext appContext;
|
||||
@Autowired BootJavaConfig config;
|
||||
@Autowired SpringSymbolIndex springIndexer;
|
||||
@Autowired(required = false) List<ICompletionEngine> completionEngines;
|
||||
@@ -93,7 +94,7 @@ public class BootLanguageServerInitializer implements InitializingBean {
|
||||
// some server intialization code. Migrate that code and get rid of the ComposableLanguageServer class
|
||||
CompositeLanguageServerComponents.Builder builder = new CompositeLanguageServerComponents.Builder();
|
||||
builder.add(new BootPropertiesLanguageServerComponents(server, params, javaElementLocationProvider, parser, yamlStructureProvider, yamlAssistContextProvider, sourceLinks));
|
||||
builder.add(new BootJavaLanguageServerComponents(server, params, sourceLinks, cuCache, adHocProperties, symbolCache, liveDataProvider, config, springIndexer));
|
||||
builder.add(new BootJavaLanguageServerComponents(appContext));
|
||||
builder.add(new SpringXMLLanguageServerComponents(server, springIndexer, params, config));
|
||||
components = builder.build(server);
|
||||
params.projectObserver.addListener(reconcileOpenDocumentsForProjectChange(server, components, params.projectFinder));
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016, 2020 Pivotal, Inc.
|
||||
* Copyright (c) 2016, 2022 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -18,18 +18,16 @@ import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
|
||||
import org.springframework.ide.vscode.boot.app.BootLanguageServerParams;
|
||||
import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
|
||||
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareLookup;
|
||||
import org.springframework.ide.vscode.boot.java.autowired.AutowiredHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.conditionals.ConditionalsLiveHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.data.DataRepositoryCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeLensEngine;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngine;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaDocumentHighlightEngine;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaDocumentSymbolHandler;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaHoverProvider;
|
||||
@@ -37,7 +35,6 @@ import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaReferencesHandler;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.BootJavaWorkspaceSymbolHandler;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CodeLensProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.HighlightProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider;
|
||||
@@ -56,19 +53,11 @@ import org.springframework.ide.vscode.boot.java.requestmapping.LiveAppURLSymbolP
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxHandlerCodeLensProvider;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.WebfluxRouteHighlightProdivder;
|
||||
import org.springframework.ide.vscode.boot.java.scope.ScopeCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippet;
|
||||
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetContext;
|
||||
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetManager;
|
||||
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SpringLiveChangeDetectionWatchdog;
|
||||
import org.springframework.ide.vscode.boot.java.utils.SymbolCache;
|
||||
import org.springframework.ide.vscode.boot.java.value.ValueCompletionProcessor;
|
||||
import org.springframework.ide.vscode.boot.java.value.ValueHoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.value.ValuePropertyReferencesProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.composable.LanguageServerComponents;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
|
||||
@@ -76,14 +65,12 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcil
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.CodeLensHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.DocumentHighlightHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.LspClient;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.ReferencesHandler;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleWorkspaceService;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
/**
|
||||
@@ -124,22 +111,14 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
private SpringProcessTracker liveProcessTracker;
|
||||
|
||||
public BootJavaLanguageServerComponents(
|
||||
SimpleLanguageServer server,
|
||||
BootLanguageServerParams serverParams,
|
||||
SourceLinks sourceLinks,
|
||||
CompilationUnitCache cuCache,
|
||||
ProjectBasedPropertyIndexProvider adHocIndexProvider,
|
||||
SymbolCache symbolCache,
|
||||
SpringProcessLiveDataProvider liveDataProvider,
|
||||
BootJavaConfig config,
|
||||
SpringSymbolIndex indexer
|
||||
ApplicationContext appContext
|
||||
) {
|
||||
this.server = server;
|
||||
this.serverParams = serverParams;
|
||||
this.server = appContext.getBean(SimpleLanguageServer.class);
|
||||
this.serverParams = appContext.getBean(BootLanguageServerParams.class);
|
||||
|
||||
projectFinder = serverParams.projectFinder;
|
||||
projectObserver = serverParams.projectObserver;
|
||||
this.cuCache = cuCache;
|
||||
this.cuCache = appContext.getBean(CompilationUnitCache.class);
|
||||
|
||||
propertyIndexProvider = serverParams.indexProvider;
|
||||
|
||||
@@ -154,29 +133,29 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
//
|
||||
|
||||
// central live data components (to coordinate live data flow)
|
||||
liveDataService = new SpringProcessConnectorService(server, liveDataProvider);
|
||||
liveDataService = appContext.getBean(SpringProcessConnectorService.class);
|
||||
|
||||
// connect the live data provider with the hovers (for data extraction and live updates)
|
||||
SpringProcessLiveDataProvider liveDataProvider = appContext.getBean(SpringProcessLiveDataProvider.class);
|
||||
SourceLinks sourceLinks = appContext.getBean(SourceLinks.class);
|
||||
hoverProvider = createHoverHandler(projectFinder, sourceLinks, liveDataProvider);
|
||||
new SpringProcessLiveHoverUpdater(server, hoverProvider, projectFinder, 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);
|
||||
new SpringProcessCommandHandler(server, liveDataService, liveDataLocalProcessConnector, appContext.getBeansOfType(SpringProcessConnectorRemote.class).values());
|
||||
|
||||
// track locally running processes and automatically connect to them if configured to do so
|
||||
BootJavaConfig config = appContext.getBean(BootJavaConfig.class);
|
||||
liveProcessTracker = new SpringProcessTracker(liveDataLocalProcessConnector, Duration.ofMillis(config.getLiveInformationAutomaticTrackingDelay()));
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
SpringSymbolIndex indexer = appContext.getBean(SpringSymbolIndex.class);
|
||||
documents.onDocumentSymbol(new BootJavaDocumentSymbolHandler(this, indexer));
|
||||
workspaceService.onWorkspaceSymbol(new BootJavaWorkspaceSymbolHandler(indexer,
|
||||
new LiveAppURLSymbolProvider(liveDataProvider)));
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2022 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.livehover.v2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
public interface ActuatorConnection {
|
||||
|
||||
String getEnvironment();
|
||||
|
||||
String getProcessID();
|
||||
|
||||
Properties getSystemProperties();
|
||||
|
||||
String getConditionalsReport() throws IOException;
|
||||
|
||||
String getRequestMappings() throws IOException;
|
||||
|
||||
String getBeans() throws IOException;
|
||||
|
||||
String getMetrics(String metric, Map<String, String> tags) throws IOException;
|
||||
|
||||
Map<?, ?> getStartup() throws IOException;
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2022 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.livehover.v2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.springframework.boot.web.client.RestTemplateBuilder;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public class HttpActuatorConnection implements ActuatorConnection {
|
||||
|
||||
private Gson gson;
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
public HttpActuatorConnection(String actuatorUrl) {
|
||||
this.restTemplate = new RestTemplateBuilder().rootUri(actuatorUrl).build();
|
||||
this.gson = new Gson();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEnvironment() {
|
||||
return restTemplate.getForObject("/env", String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProcessID() {
|
||||
return getSystemProperties().getProperty("PID");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Properties getSystemProperties() {
|
||||
JsonObject json = gson.fromJson(getEnvironment(), JsonObject.class);
|
||||
JsonArray propertySources = json.getAsJsonArray("propertySources");
|
||||
for (JsonElement jsonElement : propertySources) {
|
||||
JsonObject obj = jsonElement.getAsJsonObject();
|
||||
if ("systemProperties".equals(obj.get("name").getAsString())) {
|
||||
JsonElement props = obj.get("properties");
|
||||
Properties p = new Properties();
|
||||
for (Entry<String, JsonElement> entry : props.getAsJsonObject().entrySet()) {
|
||||
p.put(entry.getKey(), entry.getValue().getAsJsonObject().get("value").getAsString());
|
||||
}
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getConditionalsReport() throws IOException {
|
||||
return restTemplate.getForObject("/conditions", String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRequestMappings() throws IOException {
|
||||
return restTemplate.getForObject("/mappings", String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBeans() throws IOException {
|
||||
return restTemplate.getForObject("/beans", String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMetrics(String metric, Map<String, String> tags) throws IOException {
|
||||
UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromPath("/metrics/http.server.requests");
|
||||
if (tags != null) {
|
||||
for (Entry<String, String> e : tags.entrySet()) {
|
||||
uriBuilder.queryParam("tag", e.getKey() + ":" + e.getValue());
|
||||
}
|
||||
}
|
||||
String url = uriBuilder.encode().toUriString();
|
||||
return restTemplate.getForObject(url, String.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<?, ?> getStartup() throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019, 2020 Pivotal, Inc.
|
||||
* Copyright (c) 2019, 2022 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -11,6 +11,7 @@
|
||||
package org.springframework.ide.vscode.boot.java.livehover.v2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -40,13 +41,13 @@ public class SpringProcessCommandHandler {
|
||||
|
||||
private final SpringProcessConnectorService connectorService;
|
||||
private final SpringProcessConnectorLocal localProcessConnector;
|
||||
private final SpringProcessConnectorRemote remoteProcessConnector;
|
||||
private final Collection<SpringProcessConnectorRemote> remoteProcessConnectors;
|
||||
|
||||
public SpringProcessCommandHandler(SimpleLanguageServer server, SpringProcessConnectorService connectorService,
|
||||
SpringProcessConnectorLocal localProcessConnector, SpringProcessConnectorRemote remoteProcessConnector) {
|
||||
SpringProcessConnectorLocal localProcessConnector, Collection<SpringProcessConnectorRemote> remoteProcessConnectors) {
|
||||
this.connectorService = connectorService;
|
||||
this.localProcessConnector = localProcessConnector;
|
||||
this.remoteProcessConnector = remoteProcessConnector;
|
||||
this.remoteProcessConnectors = remoteProcessConnectors;
|
||||
|
||||
server.onCommand(COMMAND_LIST_PROCESSES, (params) -> {
|
||||
return getProcessCommands();
|
||||
@@ -85,12 +86,14 @@ public class SpringProcessCommandHandler {
|
||||
}
|
||||
|
||||
// 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);
|
||||
for (SpringProcessConnectorRemote remoteProcessConnector : remoteProcessConnectors) {
|
||||
RemoteBootAppData[] remoteProcesses = remoteProcessConnector.getProcesses();
|
||||
for (RemoteBootAppData remoteProcess : remoteProcesses) {
|
||||
String key = SpringProcessConnectorRemote.getProcessKey(remoteProcess);
|
||||
if (processKey.equals(key)) {
|
||||
remoteProcessConnector.connectProcess(remoteProcess);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,14 +149,17 @@ 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 = createLabel(remoteProcess.getProcessID(), SpringProcessConnectorRemote.getProcessName(remoteProcess));
|
||||
result.add(new LiveProcessCommand(COMMAND_CONNECT, processKey, label, null, remoteProcess.getProcessID()));
|
||||
for (SpringProcessConnectorRemote remoteProcessConnector : remoteProcessConnectors) {
|
||||
RemoteBootAppData[] remoteProcesses = remoteProcessConnector.getProcesses();
|
||||
for (RemoteBootAppData remoteProcess : remoteProcesses) {
|
||||
String processKey = SpringProcessConnectorRemote.getProcessKey(remoteProcess);
|
||||
if (!alreadyConnected.contains(processKey)) {
|
||||
String label = createLabel(remoteProcess.getProcessID(), SpringProcessConnectorRemote.getProcessName(remoteProcess));
|
||||
result.add(new LiveProcessCommand(COMMAND_CONNECT, processKey, label, null, remoteProcess.getProcessID()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("getProcessCommands => {}", result);
|
||||
return CompletableFuture.completedFuture((Object[]) result.toArray(new Object[result.size()]));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
package org.springframework.ide.vscode.boot.java.livehover.v2;
|
||||
|
||||
public class SpringProcessConnectorOverHttp implements SpringProcessConnector {
|
||||
|
||||
private final String processKey;
|
||||
private final String actuatorUrl;
|
||||
private final String urlScheme;
|
||||
private final String port;
|
||||
private final String projectName;
|
||||
|
||||
// not final, might be updated with data from JMX process, if not initially set
|
||||
private String processID;
|
||||
private String processName;
|
||||
private String host;
|
||||
|
||||
private HttpActuatorConnection actuatorConnection;
|
||||
|
||||
|
||||
public SpringProcessConnectorOverHttp(String processKey, String actuatorUrl,
|
||||
String urlScheme, String processID, String processName, String projectName, String host, String port) {
|
||||
|
||||
this.processKey = processKey;
|
||||
|
||||
this.actuatorUrl = actuatorUrl;
|
||||
this.urlScheme = urlScheme;
|
||||
this.processID = processID;
|
||||
this.processName = processName;
|
||||
this.projectName = projectName;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProcessKey() {
|
||||
return processKey;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect() throws Exception {
|
||||
actuatorConnection = new HttpActuatorConnection(actuatorUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SpringProcessLiveData refresh(SpringProcessLiveData currentData) throws Exception {
|
||||
if (actuatorConnection != null) {
|
||||
SpringProcessLiveData liveData = new SpringProcessLiveDataExtractorOverHttp().retrieveLiveData(actuatorConnection, processID, processName, urlScheme, host, null, port, currentData);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Exception("no live data received, lets try again");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() throws Exception {
|
||||
actuatorConnection = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addConnectorChangeListener(SpringProcessConnectionChangeListener listener) {
|
||||
// Useless in the case of Http connection
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeConnectorChangeListener(SpringProcessConnectionChangeListener listener) {
|
||||
// Useless in the case of Http connection
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProjectName() {
|
||||
return projectName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProcessId() {
|
||||
return processID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProcessName() {
|
||||
return processName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SpringProcessConnectorOverHttp [actuatorURL=" + actuatorUrl + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019, 2020 Pivotal, Inc.
|
||||
* Copyright (c) 2019, 2022 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
@@ -20,7 +20,6 @@ import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.Settings;
|
||||
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -169,28 +168,26 @@ public class SpringProcessConnectorRemote {
|
||||
private static Logger logger = LoggerFactory.getLogger(SpringProcessConnectorRemote.class);
|
||||
|
||||
/**
|
||||
* We keep the remote app instances in a Map indexed by the json daya. This allows us to
|
||||
* We keep the remote app instances in a Map indexed by the json data. This allows us to
|
||||
* return the same instance(s) repeatedly as long as the data does not change.
|
||||
*/
|
||||
private final Map<RemoteBootAppData, String> remoteAppInstances;
|
||||
|
||||
private final SpringProcessConnectorService processConnectorService;
|
||||
|
||||
public SpringProcessConnectorRemote(SimpleLanguageServer server, SpringProcessConnectorService processConnector) {
|
||||
this.processConnectorService = processConnector;
|
||||
this.remoteAppInstances = new HashMap<>();
|
||||
|
||||
server.getWorkspaceService().onDidChangeConfiguraton(this::handleSettings);
|
||||
}
|
||||
|
||||
private synchronized void handleSettings(Settings settings) {
|
||||
|
||||
/**
|
||||
* Replaces existing remote app data map with the new remote app data. Empty array of appData will result in no remote apps.
|
||||
* @param appData new remote app data
|
||||
*/
|
||||
final public synchronized void updateApps(RemoteBootAppData[] appData) {
|
||||
logger.info("updating settings for remote processses to track - start");
|
||||
|
||||
RemoteBootAppData[] appData = settings.getAs(RemoteBootAppData[].class, "boot-java", "remote-apps");
|
||||
if (appData == null) {
|
||||
//Avoid NPE
|
||||
appData = new RemoteBootAppData[0];
|
||||
}
|
||||
|
||||
|
||||
// remove outdated remote apps
|
||||
Set<RemoteBootAppData> newAppData = new HashSet<>(Arrays.asList(appData));
|
||||
|
||||
@@ -221,7 +218,7 @@ public class SpringProcessConnectorRemote {
|
||||
|
||||
logger.info("updating settings for remote processses to track - done");
|
||||
}
|
||||
|
||||
|
||||
public static String getProcessName(RemoteBootAppData appData) {
|
||||
if (StringUtils.hasText(appData.getProcessName())) {
|
||||
return appData.getProcessName();
|
||||
@@ -247,8 +244,13 @@ public class SpringProcessConnectorRemote {
|
||||
String urlScheme = remoteProcess.getUrlScheme();
|
||||
// boolean keepChecking = _appData.isKeepChecking();
|
||||
|
||||
SpringProcessConnectorOverJMX connector = new SpringProcessConnectorOverJMX(processKey, jmxURL, urlScheme, processID, processName, null, host, port);
|
||||
processConnectorService.connectProcess(processKey, connector);
|
||||
if (jmxURL.startsWith("http")) {
|
||||
SpringProcessConnectorOverHttp connector = new SpringProcessConnectorOverHttp(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);
|
||||
processConnectorService.connectProcess(processKey, connector);
|
||||
}
|
||||
}
|
||||
|
||||
public RemoteBootAppData[] getProcesses() {
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2022 VMware, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* VMware, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.livehover.v2;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
public class SpringProcessLiveDataExtractorOverHttp {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SpringProcessLiveDataExtractorOverJMX.class);
|
||||
|
||||
// NOTE: Gson-based serialisation replaces the old Jackson ObjectMapper. Not sure if this makes a difference in the long run, but to retain the same output that Jackson Object Mapper
|
||||
// was generating during serialisation, some configuration in Gson is required, as the default behaviour of Gson is different than Object Mapper.
|
||||
// Namely: Object Mapper does not escape Html, whereas Gson does by default (for example
|
||||
// '=' in Gson appears as '\u003d')
|
||||
private final Gson gson = new GsonBuilder()
|
||||
.disableHtmlEscaping()
|
||||
.create();
|
||||
|
||||
/**
|
||||
* @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
|
||||
* @param host should always be != null
|
||||
* @param contextPath if null, will be determined searching existing mbeans for that information (for local processes)
|
||||
* @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,
|
||||
String urlScheme, String host, String contextPath, String port, SpringProcessLiveData currentData) {
|
||||
|
||||
try {
|
||||
|
||||
String environment = connection.getEnvironment();
|
||||
String[] activeProfiles = getActiveProfiles(environment);
|
||||
|
||||
LiveProperties properties = getProperties(environment);
|
||||
|
||||
if (processID == null) {
|
||||
processID = connection.getProcessID();
|
||||
}
|
||||
|
||||
if (processName == null) {
|
||||
Properties systemProperties = connection.getSystemProperties();
|
||||
if (systemProperties != null) {
|
||||
String javaCommand = getJavaCommand(systemProperties);
|
||||
processName = getProcessName(javaCommand);
|
||||
}
|
||||
}
|
||||
|
||||
LiveConditional[] conditionals = getConditionals(connection, processID, processName);
|
||||
LiveRequestMapping[] requestMappings = getRequestMappings(connection);
|
||||
LiveBeansModel beans = getBeans(connection);
|
||||
LiveMetricsModel metrics = getMetrics(connection);
|
||||
StartupMetricsModel startup = getStartupMetrics(connection, currentData == null ? null : currentData.getStartupMetrics());
|
||||
|
||||
if (contextPath == null) {
|
||||
contextPath = getContextPath(environment);
|
||||
}
|
||||
|
||||
// if (port == null) {
|
||||
// port = getPort(connection, environment);
|
||||
// }
|
||||
|
||||
return new SpringProcessLiveData(
|
||||
processName,
|
||||
processID,
|
||||
contextPath,
|
||||
urlScheme,
|
||||
port,
|
||||
host,
|
||||
beans,
|
||||
activeProfiles,
|
||||
requestMappings,
|
||||
conditionals,
|
||||
properties,
|
||||
metrics,
|
||||
startup);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("error reading live data from: " + processID + " - " + processName, e);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private LiveMetricsModel getMetrics(ActuatorConnection connection) {
|
||||
|
||||
return new LiveMetricsModel() {
|
||||
|
||||
@Override
|
||||
public RequestMappingMetrics getRequestMappingMetrics(String[] paths, String[] requestMethods) {
|
||||
try {
|
||||
if (paths.length == 0) {
|
||||
return null;
|
||||
}
|
||||
Map<String, String> tags = new HashMap<>();
|
||||
tags.put("uri", String.join(",", paths));
|
||||
if (requestMethods.length > 0) {
|
||||
tags.put("method", String.join(",", requestMethods));
|
||||
}
|
||||
|
||||
String metricsData = connection.getMetrics("http.server.requests", tags);
|
||||
|
||||
return RequestMappingMetrics.parse(metricsData);
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
private StartupMetricsModel getStartupMetrics(ActuatorConnection connection, StartupMetricsModel currentStartup) {
|
||||
if (currentStartup != null) {
|
||||
return currentStartup;
|
||||
}
|
||||
try {
|
||||
Map<?,?> r = connection.getStartup();
|
||||
if (r != null) {
|
||||
return StartupMetricsModel.parse(r);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
} catch (Exception e) {
|
||||
log.error("", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getProcessName(String command) throws Exception {
|
||||
if (command != null) {
|
||||
int space = command.indexOf(' ');
|
||||
if (space >= 0) {
|
||||
command = command.substring(0, space);
|
||||
}
|
||||
command = command.trim();
|
||||
if (!"".equals(command)) {
|
||||
return command;
|
||||
}
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
public String getJavaCommand(Properties systemProperties) {
|
||||
return (String) systemProperties.get("sun.java.command");
|
||||
}
|
||||
|
||||
public LiveBeansModel getBeans(ActuatorConnection connection) {
|
||||
try {
|
||||
String json = connection.getBeans();
|
||||
|
||||
if (json instanceof String) {
|
||||
return LiveBeansModel.parse((String) json);
|
||||
} else {
|
||||
return LiveBeansModel.parse(gson.toJson(json));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
} catch (Exception e) {
|
||||
log.error("Error parsing beans", e);
|
||||
}
|
||||
return LiveBeansModel.builder().build();
|
||||
}
|
||||
|
||||
public LiveRequestMapping[] getRequestMappings(ActuatorConnection connection) throws Exception {
|
||||
try {
|
||||
String mappings = connection.getRequestMappings();
|
||||
return parseRequestMappingsJson(mappings, "2.x");
|
||||
} catch (IOException e) {
|
||||
//ignore.. app stopped
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private LiveRequestMapping[] parseRequestMappingsJson(String json, String bootVersion) {
|
||||
JSONObject obj = new JSONObject(json);
|
||||
if (bootVersion.equals("2.x")) {
|
||||
return LiveRequestMappingBoot2xParser.parse(obj);
|
||||
} else { //1.x
|
||||
List<LiveRequestMapping> result = new ArrayList<>();
|
||||
Iterator<String> keys = obj.keys();
|
||||
while (keys.hasNext()) {
|
||||
String rawKey = keys.next();
|
||||
JSONObject value = obj.getJSONObject(rawKey);
|
||||
result.add(new LiveRequestMappingBoot1xRequestMapping(rawKey, value));
|
||||
}
|
||||
return (LiveRequestMapping[]) result.toArray(new LiveRequestMapping[result.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
public LiveConditional[] getConditionals(ActuatorConnection connection, String processId, String processName) {
|
||||
try {
|
||||
String report = connection.getConditionalsReport();
|
||||
return LiveConditionalParser.parse(report, processId, processName);
|
||||
} catch (IOException e) {
|
||||
//ignore. Happens a lot when apps are stopped while we try to talk to them.
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String[] getActiveProfiles(String environment) {
|
||||
try {
|
||||
if (environment != null) {
|
||||
JSONObject env = new JSONObject(environment);
|
||||
|
||||
Object _profiles = env.opt("activeProfiles"); //Boot 2.0
|
||||
if (_profiles == null) {
|
||||
_profiles = env.opt("profiles"); //Boot 1.5
|
||||
}
|
||||
|
||||
if (_profiles instanceof JSONArray) {
|
||||
JSONArray profiles = (JSONArray) _profiles;
|
||||
ImmutableList.Builder<String> list = ImmutableList.builder();
|
||||
for (Object object : profiles) {
|
||||
if (object instanceof String) {
|
||||
list.add((String) object);
|
||||
}
|
||||
}
|
||||
return list.build().toArray(new String[0]);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("error resolving profiles from env", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public LiveProperties getProperties(String environment) throws Exception {
|
||||
|
||||
try {
|
||||
if (environment != null) {
|
||||
return LivePropertiesJsonParser.parseProperties(environment);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("error resolving live properties from environment endpoint", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getContextPath(String environment) throws Exception {
|
||||
return environment != null ? LiveContextPathUtil.getContextPath("2.x", environment) : null;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user