Preliminary implementation of boot-dash menus...

for live process connection management
This commit is contained in:
Kris De Volder
2019-10-28 10:47:40 -07:00
parent ae1bb85aa4
commit 9fae675c84
7 changed files with 145 additions and 151 deletions

View File

@@ -18,33 +18,34 @@ public class LiveProcessCommand {
private String processKey;
private String label;
private String action;
private String projectName;
public String getProcessKey() {
return processKey;
public LiveProcessCommand(String action, String processKey, String label, String projectName) {
super();
this.processKey = processKey;
this.label = label;
this.action = action;
this.projectName = projectName;
}
public void setProcessKey(String processKey) {
this.processKey = processKey;
public String getProcessKey() {
return processKey;
}
public String getLabel() {
return label;
}
public void setLabel(String label) {
this.label = label;
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
@Override
public String toString() {
return "LiveProcessCommand [processKey=" + processKey + ", label=" + label + ", action=" + action + "]";
}
public String getProjectName() {
return projectName;
}
}

View File

@@ -126,19 +126,8 @@ public class SpringProcessCommandHandler {
for (SpringProcessConnector process : connectedProcesses) {
String processKey = process.getProcessKey();
String label = process.getLabel();
LiveProcessCommand refreshCommand = new LiveProcessCommand();
refreshCommand.setAction(COMMAND_REFRESH);
refreshCommand.setLabel(label);
refreshCommand.setProcessKey(processKey);
result.add(refreshCommand);
LiveProcessCommand disconnectCommand = new LiveProcessCommand();
disconnectCommand.setAction(COMMAND_DISCONNECT);
disconnectCommand.setLabel(label);
disconnectCommand.setProcessKey(processKey);
result.add(disconnectCommand);
result.add(new LiveProcessCommand(COMMAND_REFRESH, processKey, label, process.getProjectName()));
result.add(new LiveProcessCommand(COMMAND_DISCONNECT, processKey, label, process.getProjectName()));
alreadyConnected.add(processKey);
}
@@ -149,13 +138,8 @@ public class SpringProcessCommandHandler {
String processKey = localProcess.getProcessKey();
if (!alreadyConnected.contains(processKey)) {
String label = localProcess.getLabel();
String action = COMMAND_CONNECT;
LiveProcessCommand command = new LiveProcessCommand();
command.setAction(action);
command.setLabel(label);
command.setProcessKey(processKey);
LiveProcessCommand command = new LiveProcessCommand(COMMAND_CONNECT, processKey, label, localProcess.getProjectName());
result.add(command);
}
}
@@ -167,14 +151,7 @@ public class SpringProcessCommandHandler {
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);
result.add(new LiveProcessCommand(COMMAND_CONNECT, processKey, label, null));
}
}
log.info("getProcessCommands => {}", result);

View File

@@ -24,5 +24,6 @@ public interface SpringProcessConnector {
void addConnectorChangeListener(SpringProcessConnectionChangeListener listener);
void removeConnectorChangeListener(SpringProcessConnectionChangeListener listener);
String getProjectName();
}

View File

@@ -41,28 +41,6 @@ public class SpringProcessConnectorLocal {
private static final String LOCAL_CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress";
/**
* this property is automatically set my language servers to their own processes
* to avoid the spring boot language server to connect to itself or another language server process
*
* this is primarily an optimization to avoid that overhead of trying to connect to such a process
*/
private static final String LANGUAGE_SERVER_PROPERTY = "sts4.languageserver.name";
/**
* this property can be set to a running spring boot app at startup to indicatew that the
* process of this running boot app belongs to a specific project
*
* in that case, the running process is only connected if the workspace contains that project
*/
private static final String SPRING_APP_PROJECT_NAME_PROPERTY = "spring.boot.project.name";
/**
* common prefix for the vm descriptor display name of Eclipse processes, we can filter that out
* of the list of processes to connect to immediately to avoid further processing
*/
private static final String ECLIPSE_PROCESS_DISPLAY_NAME_PREFIX = "org.eclipse.equinox.launcher.Main";
private final Collection<String> projects;
private final Set<SpringProcessDescriptor> processes;
@@ -180,10 +158,7 @@ public class SpringProcessConnectorLocal {
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (SpringProcessDescriptor process : processes) {
CompletableFuture<SpringProcessStatus> checkStatusFuture = checkStatus(process);
CompletableFuture<Void> result = checkStatusFuture.thenAccept((status) -> process.setStatus(status));
futures.add(result);
futures.add(process.updateStatus(projects::contains));
}
CompletableFuture<Void> allStatusUpdates = CompletableFuture.allOf((CompletableFuture[]) futures.toArray(new CompletableFuture[futures.size()]));
@@ -196,39 +171,6 @@ public class SpringProcessConnectorLocal {
}
}
private CompletableFuture<SpringProcessStatus> checkStatus(SpringProcessDescriptor descriptor) {
return CompletableFuture.supplyAsync(() -> {
VirtualMachine vm = null;
try {
vm = VirtualMachine.attach(descriptor.getVm());
if (shouldIgnore(descriptor.getVm(), vm)) {
return SpringProcessStatus.IGNORE;
}
if (shouldAutoConnect(descriptor.getVm(), vm)) {
return SpringProcessStatus.AUTO_CONNECT;
}
return SpringProcessStatus.REGULAR;
}
catch (Exception e) {
return SpringProcessStatus.IGNORE;
}
finally {
if (vm != null) {
try {
vm.detach();
}
catch (Exception e) {
log.error("error detaching from vm: " + descriptor.getVm().id(), e);
}
}
}
});
}
public void connectProcess(SpringProcessDescriptor descriptor) {
VirtualMachine vm = null;
VirtualMachineDescriptor vmDescriptor = descriptor.getVm();
@@ -257,7 +199,7 @@ public class SpringProcessConnectorLocal {
String urlScheme = "http";
SpringProcessConnectorOverJMX connector = new SpringProcessConnectorOverJMX(
descriptor.getProcessKey(), jmxAddress, urlScheme, processID, processName, null, null);
descriptor.getProcessKey(), jmxAddress, urlScheme, processID, processName, descriptor.getProjectName(), null, null);
this.processConnectorService.connectProcess(descriptor.getProcessKey(), connector);
}
@@ -281,47 +223,6 @@ public class SpringProcessConnectorLocal {
return this.processConnectorService.isConnected(processKey);
}
private boolean shouldIgnore(VirtualMachineDescriptor vmDescriptor, VirtualMachine vm) {
try {
String displayName = vmDescriptor.displayName();
if (displayName != null && displayName.startsWith(ECLIPSE_PROCESS_DISPLAY_NAME_PREFIX)) {
log.info("Eclipse process found, do not connect: " + vmDescriptor.id());
return true;
}
Properties systemProperties = vm.getSystemProperties();
Object languageServerIndicatorProperty = systemProperties.get(LANGUAGE_SERVER_PROPERTY);
if (languageServerIndicatorProperty != null) {
log.info("language server process found, do not connect: " + vmDescriptor.id());
return true;
}
}
catch (Exception e) {
return true;
}
return false;
}
private boolean shouldAutoConnect(VirtualMachineDescriptor vmDescriptor, VirtualMachine vm) {
try {
Properties systemProperties = vm.getSystemProperties();
Object projectNameProperty = systemProperties.get(SPRING_APP_PROJECT_NAME_PROPERTY);
if (projectNameProperty instanceof String) {
log.info("Spring boot process found: " + projectNameProperty);
return this.projects.contains((String) projectNameProperty);
}
}
catch (Exception e) {
return false;
}
return false;
}
private String getProcessID(VirtualMachineDescriptor descriptor) {
return descriptor.id();
}

View File

@@ -39,6 +39,8 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector {
private final String jmxURL;
private final String urlScheme;
private final String port;
final private String projectName;
// not final, might be updated with data from JMX process, if not initially set
private String processID;
@@ -51,9 +53,9 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector {
private JMXServiceURL jmxServiceURL;
private final NotificationListener notificationListener;
public SpringProcessConnectorOverJMX(String processKey, String jmxURL,
String urlScheme, String processID, String processName, String host, String port) {
String urlScheme, String processID, String processName, String projectName, String host, String port) {
this.processKey = processKey;
@@ -61,6 +63,7 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector {
this.urlScheme = urlScheme;
this.processID = processID;
this.processName = processName;
this.projectName = projectName;
this.host = host;
this.port = port;
@@ -176,4 +179,9 @@ public class SpringProcessConnectorOverJMX implements SpringProcessConnector {
}
}
@Override
public String getProjectName() {
return projectName;
}
}

View File

@@ -199,7 +199,7 @@ public class SpringProcessConnectorRemote {
String urlScheme = remoteProcess.getUrlScheme();
// boolean keepChecking = _appData.isKeepChecking();
SpringProcessConnectorOverJMX connector = new SpringProcessConnectorOverJMX(processKey, jmxURL, urlScheme, processID, processName, host, port);
SpringProcessConnectorOverJMX connector = new SpringProcessConnectorOverJMX(processKey, jmxURL, urlScheme, processID, processName, null, host, port);
processConnectorService.connectProcess(processKey, connector);
}

View File

@@ -10,20 +10,52 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.livehover.v2;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
/**
* @author Martin Lippert
*/
@SuppressWarnings("restriction")
public class SpringProcessDescriptor {
private static final Logger log = LoggerFactory.getLogger(SpringProcessDescriptor.class);
/**
* this property is automatically set my language servers to their own processes
* to avoid the spring boot language server to connect to itself or another language server process
*
* this is primarily an optimization to avoid that overhead of trying to connect to such a process
*/
private static final String LANGUAGE_SERVER_PROPERTY = "sts4.languageserver.name";
/**
* this property can be set to a running spring boot app at startup to indicatew that the
* process of this running boot app belongs to a specific project
*
* in that case, the running process is only connected if the workspace contains that project
*/
private static final String SPRING_APP_PROJECT_NAME_PROPERTY = "spring.boot.project.name";
/**
* common prefix for the vm descriptor display name of Eclipse processes, we can filter that out
* of the list of processes to connect to immediately to avoid further processing
*/
private static final String ECLIPSE_PROCESS_DISPLAY_NAME_PREFIX = "org.eclipse.equinox.launcher.Main";
private final String processKey;
private final String processID;
private final String processName;
private final VirtualMachineDescriptor vm;
private SpringProcessStatus status;
private String projectName;
public SpringProcessDescriptor(String processKey, String processID, String processName, VirtualMachineDescriptor vm) {
this.processKey = processKey;
@@ -53,11 +85,7 @@ public class SpringProcessDescriptor {
public String getLabel() {
return processID + " (" + processName + ")";
}
public void setStatus(SpringProcessStatus status) {
this.status = status;
}
public SpringProcessStatus getStatus() {
return this.status;
}
@@ -83,8 +111,86 @@ public class SpringProcessDescriptor {
return false;
return true;
}
public CompletableFuture<Void> updateStatus(Predicate<String> projectIsKnown) {
return CompletableFuture.supplyAsync(() -> {
this.status = checkStatus(projectIsKnown);
return null;
});
}
private SpringProcessStatus checkStatus(Predicate<String> projectIsKnown) {
VirtualMachine vm = null;
try {
vm = VirtualMachine.attach(this.getVm());
if (shouldIgnore(this.getVm(), vm)) {
return SpringProcessStatus.IGNORE;
}
if (shouldAutoConnect(this.getVm(), vm, projectIsKnown)) {
return SpringProcessStatus.AUTO_CONNECT;
}
return SpringProcessStatus.REGULAR;
}
catch (Exception e) {
return SpringProcessStatus.IGNORE;
}
finally {
if (vm != null) {
try {
vm.detach();
}
catch (Exception e) {
log.error("error detaching from vm: " + this.getVm().id(), e);
}
}
}
}
private boolean shouldIgnore(VirtualMachineDescriptor vmDescriptor, VirtualMachine vm) {
try {
String displayName = vmDescriptor.displayName();
if (displayName != null && displayName.startsWith(ECLIPSE_PROCESS_DISPLAY_NAME_PREFIX)) {
log.info("Eclipse process found, do not connect: " + vmDescriptor.id());
return true;
}
Properties systemProperties = vm.getSystemProperties();
Object languageServerIndicatorProperty = systemProperties.get(LANGUAGE_SERVER_PROPERTY);
if (languageServerIndicatorProperty != null) {
log.info("language server process found, do not connect: " + vmDescriptor.id());
return true;
}
}
catch (Exception e) {
return true;
}
return false;
}
private boolean shouldAutoConnect(VirtualMachineDescriptor vmDescriptor, VirtualMachine vm, Predicate<String> projectIsKnown) {
try {
Properties systemProperties = vm.getSystemProperties();
Object projectName = systemProperties.get(SPRING_APP_PROJECT_NAME_PROPERTY);
if (projectName instanceof String) {
log.info("Spring boot process found: " + projectName);
this.projectName = (String) projectName;
return projectIsKnown.test((String) projectName);
}
}
catch (Exception e) {
return false;
}
return false;
}
public String getProjectName() {
return projectName;
}
}