Prefer connecting via JMX to process. New UI for show/refresh/hide
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
|
||||
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
|
||||
@@ -9,8 +9,8 @@ org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nul
|
||||
org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
|
||||
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
|
||||
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=11
|
||||
org.eclipse.jdt.core.compiler.compliance=11
|
||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
|
||||
org.eclipse.jdt.core.compiler.compliance=17
|
||||
org.eclipse.jdt.core.compiler.problem.APILeak=warning
|
||||
org.eclipse.jdt.core.compiler.problem.annotatedTypeArgumentToUnannotated=info
|
||||
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
|
||||
@@ -109,4 +109,4 @@ org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
|
||||
org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
|
||||
org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
|
||||
org.eclipse.jdt.core.compiler.release=enabled
|
||||
org.eclipse.jdt.core.compiler.source=11
|
||||
org.eclipse.jdt.core.compiler.source=17
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512.008 512.008" xml:space="preserve" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <g> <path style="fill:#868491;" d="M280.01,464.008h-48c-4.422,0-8,3.578-8,8v8c0,17.648,14.352,32,32,32s32-14.352,32-32v-8 C288.01,467.586,284.432,464.008,280.01,464.008z"></path> </g> <g> <path style="fill:#5C546A;" d="M256.002,464.008H232.01c-4.422,0-8,3.578-8,8v8c0,17.646,14.347,31.995,31.992,31.999 L256.002,464.008L256.002,464.008z"></path> </g> <g> <path style="fill:#B4B6BC;" d="M381.983,49.895c-37.391-35.141-86.367-52.742-137.734-49.52 C150.639,6.2,75.014,84.286,72.092,178.149c-2.156,69.547,34.375,133.578,95.344,167.117c15.156,8.344,24.57,24.367,24.57,41.828 v60.914c0,17.648,14.352,32,32,32h64c17.648,0,32-14.352,32-32v-60.914c0-17.445,9.484-33.508,24.766-41.93 c58.742-32.406,95.234-94.156,95.234-161.156C440.006,133.43,418.858,84.547,381.983,49.895z"></path> </g> <g> <circle style="fill:#FFFFFF;" cx="256.002" cy="184.008" r="128"></circle> </g> <g> <g> <path style="fill:#868491;" d="M283.986,232.008c-10.633,0-16.07-6.203-20.031-10.727c-3.492-3.984-4.883-5.273-8-5.273 c-3.102,0-4.484,1.289-7.961,5.266c-3.734,4.273-9.383,10.734-20.016,10.734c-10.625,0-16.273-6.461-20.008-10.734 c-3.477-3.977-4.859-5.266-7.961-5.266c-4.422,0-8-3.578-8-8s3.578-8,8-8c10.625,0,16.273,6.461,20.008,10.734 c3.477,3.977,4.859,5.266,7.961,5.266s4.492-1.289,7.969-5.266c3.734-4.273,9.383-10.734,20.008-10.734 c10.633,0,16.07,6.203,20.031,10.727c3.492,3.984,4.883,5.273,8,5.273c3.109,0,4.5-1.289,7.992-5.266 c3.742-4.273,9.391-10.734,20.031-10.734c4.422,0,8,3.578,8,8s-3.578,8-8,8c-3.117,0-4.508,1.289-8,5.273 C300.267,225.555,294.619,232.008,283.986,232.008z"></path> </g> </g> <g> <g> <path style="fill:#5C546A;" d="M232.002,384.008c-3.68,0-6.992-2.555-7.813-6.297l-40-184c-0.938-4.32,1.805-8.578,6.117-9.523 c4.344-0.953,8.578,1.805,9.523,6.117l40,184c0.938,4.32-1.805,8.578-6.117,9.523 C233.135,383.954,232.564,384.008,232.002,384.008z"></path> </g> </g> <g> <g> <path style="fill:#5C546A;" d="M280.017,384.008c-0.563,0-1.133-0.055-1.711-0.18c-4.313-0.945-7.055-5.203-6.117-9.523l40-184 c0.945-4.313,5.164-7.063,9.523-6.117c4.313,0.945,7.055,5.203,6.117,9.523l-40,184 C287.01,381.454,283.697,384.008,280.017,384.008z"></path> </g> </g> <g> <path style="fill:#B4B6BC;" d="M190.633,376.008c0.851,3.593,1.374,7.293,1.374,11.086v60.914c0,17.648,14.352,32,32,32h64 c17.648,0,32-14.352,32-32v-60.914c0-3.79,0.527-7.491,1.384-11.086H190.633z"></path> </g> <g> <path style="fill:#868491;" d="M256.002,376.008h-65.369c0.851,3.593,1.374,7.293,1.374,11.086v60.914c0,17.648,14.352,32,32,32 h31.995V376.008z"></path> </g> <g> <g> <rect x="256.002" y="400.008" style="fill:#868491;" width="64" height="16"></rect> </g> </g> <g> <g> <rect x="256.006" y="432.008" style="fill:#868491;" width="64" height="16"></rect> </g> </g> <g> <g> <rect x="192.002" y="400.008" style="fill:#5C546A;" width="64" height="16"></rect> </g> </g> <g> <g> <rect x="192.002" y="432.008" style="fill:#5C546A;" width="64" height="16"></rect> </g> </g> </g> </g></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 512.008 512.008" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#868491;" d="M280.01,464.008h-48c-4.422,0-8,3.578-8,8v8c0,17.648,14.352,32,32,32s32-14.352,32-32v-8
|
||||
C288.01,467.586,284.432,464.008,280.01,464.008z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#5C546A;" d="M256.002,464.008H232.01c-4.422,0-8,3.578-8,8v8c0,17.646,14.347,31.995,31.992,31.999
|
||||
L256.002,464.008L256.002,464.008z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FFCD00;" d="M381.983,49.895c-37.391-35.141-86.367-52.742-137.734-49.52
|
||||
C150.639,6.2,75.014,84.286,72.092,178.149c-2.156,69.547,34.375,133.578,95.344,167.117c15.156,8.344,24.57,24.367,24.57,41.828
|
||||
v60.914c0,17.648,14.352,32,32,32h64c17.648,0,32-14.352,32-32v-60.914c0-17.445,9.484-33.508,24.766-41.93
|
||||
c58.742-32.406,95.234-94.156,95.234-161.156C440.006,133.43,418.858,84.547,381.983,49.895z"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle style="fill:#FFE671;" cx="256.002" cy="184.008" r="128"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#FFFFFF;" d="M283.986,232.008c-10.633,0-16.07-6.203-20.031-10.727c-3.492-3.984-4.883-5.273-8-5.273
|
||||
c-3.102,0-4.484,1.289-7.961,5.266c-3.734,4.273-9.383,10.734-20.016,10.734c-10.625,0-16.273-6.461-20.008-10.734
|
||||
c-3.477-3.977-4.859-5.266-7.961-5.266c-4.422,0-8-3.578-8-8s3.578-8,8-8c10.625,0,16.273,6.461,20.008,10.734
|
||||
c3.477,3.977,4.859,5.266,7.961,5.266s4.492-1.289,7.969-5.266c3.734-4.273,9.383-10.734,20.008-10.734
|
||||
c10.633,0,16.07,6.203,20.031,10.727c3.492,3.984,4.883,5.273,8,5.273c3.109,0,4.5-1.289,7.992-5.266
|
||||
c3.742-4.273,9.391-10.734,20.031-10.734c4.422,0,8,3.578,8,8s-3.578,8-8,8c-3.117,0-4.508,1.289-8,5.273
|
||||
C300.267,225.555,294.619,232.008,283.986,232.008z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#5C546A;" d="M232.002,384.008c-3.68,0-6.992-2.555-7.813-6.297l-40-184c-0.938-4.32,1.805-8.578,6.117-9.523
|
||||
c4.344-0.953,8.578,1.805,9.523,6.117l40,184c0.938,4.32-1.805,8.578-6.117,9.523
|
||||
C233.135,383.954,232.564,384.008,232.002,384.008z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#5C546A;" d="M280.017,384.008c-0.563,0-1.133-0.055-1.711-0.18c-4.313-0.945-7.055-5.203-6.117-9.523l40-184
|
||||
c0.945-4.313,5.164-7.063,9.523-6.117c4.313,0.945,7.055,5.203,6.117,9.523l-40,184
|
||||
C287.01,381.454,283.697,384.008,280.017,384.008z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#B4B6BC;" d="M190.633,376.008c0.851,3.593,1.374,7.293,1.374,11.086v60.914c0,17.648,14.352,32,32,32h64
|
||||
c17.648,0,32-14.352,32-32v-60.914c0-3.79,0.527-7.491,1.384-11.086H190.633z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#868491;" d="M256.002,376.008h-65.369c0.851,3.593,1.374,7.293,1.374,11.086v60.914c0,17.648,14.352,32,32,32
|
||||
h31.995V376.008z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="256.002" y="400.008" style="fill:#868491;" width="64" height="16"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="256.006" y="432.008" style="fill:#868491;" width="64" height="16"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="192.002" y="400.008" style="fill:#5C546A;" width="64" height="16"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="192.002" y="432.008" style="fill:#5C546A;" width="64" height="16"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2015, 2022 Pivotal, Inc.
|
||||
* Copyright (c) 2015, 2024 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
|
||||
@@ -10,7 +10,6 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.eclipse.boot.dash;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -23,6 +22,7 @@ import org.eclipse.debug.core.ILaunchConfiguration;
|
||||
import org.eclipse.debug.core.model.IProcess;
|
||||
import org.eclipse.jdt.core.IJavaProject;
|
||||
import org.eclipse.jdt.core.JavaCore;
|
||||
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
|
||||
import org.eclipse.jface.resource.ImageDescriptor;
|
||||
import org.eclipse.jface.resource.ImageRegistry;
|
||||
import org.eclipse.ui.plugin.AbstractUIPlugin;
|
||||
@@ -36,15 +36,12 @@ import org.springframework.ide.eclipse.boot.dash.model.BootDashModelContext;
|
||||
import org.springframework.ide.eclipse.boot.dash.model.BootDashViewModel;
|
||||
import org.springframework.ide.eclipse.boot.dash.model.DefaultBootDashModelContext;
|
||||
import org.springframework.ide.eclipse.boot.dash.model.LocalBootDashModel;
|
||||
import org.springframework.ide.eclipse.boot.dash.model.RunState;
|
||||
import org.springframework.ide.eclipse.boot.dash.model.RunTargets;
|
||||
import org.springframework.ide.eclipse.boot.dash.util.LaunchConfRunStateTracker;
|
||||
import org.springframework.ide.eclipse.boot.dash.util.RunStateTracker.RunStateListener;
|
||||
import org.springframework.ide.eclipse.boot.launch.BootLaunchConfigurationDelegate;
|
||||
import org.springsource.ide.eclipse.commons.livexp.util.Log;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
/**
|
||||
* The activator class controls the plug-in life cycle
|
||||
*/
|
||||
@@ -195,35 +192,46 @@ public class BootDashActivator extends AbstractUIPlugin {
|
||||
|
||||
@Override
|
||||
public void stateChanged(ILaunchConfiguration owner) {
|
||||
if (BootLaunchConfigurationDelegate.getEnableJmx(owner) && BootLaunchConfigurationDelegate.getAutoConnect(owner)) {
|
||||
if (BootLaunchConfigurationDelegate.getEnableJmx(owner)) {
|
||||
IJavaProject project = JavaCore.create(BootLaunchConfigurationDelegate.getProject(owner));
|
||||
try {
|
||||
if (project != null && Arrays.stream(project.getResolvedClasspath(true)).anyMatch(BootPropertyTester::isActuatorJar)) {
|
||||
LocalBootDashModel localModel = (LocalBootDashModel) getModel().getSectionByTargetId(RunTargets.LOCAL.getId());
|
||||
LaunchConfRunStateTracker tracker = localModel.getLaunchConfRunStateTracker();
|
||||
RunState state = tracker.getState(owner);
|
||||
if (state == RunState.RUNNING || state == RunState.DEBUGGING) {
|
||||
switch (tracker.getState(owner)) {
|
||||
case RUNNING:
|
||||
case DEBUGGING:
|
||||
for (ILaunch l : DebugPlugin.getDefault().getLaunchManager().getLaunches()) {
|
||||
if (l.getLaunchConfiguration() == owner) {
|
||||
for (IProcess p : l.getProcesses()) {
|
||||
String pid = p.getAttribute(IProcess.ATTR_PROCESS_ID);
|
||||
if (pid != null) {
|
||||
CommandInfo cmd = new CommandInfo("sts/livedata/connect",
|
||||
Map.of("processKey", pid));
|
||||
|
||||
// The delay seems to be necessary for the moment. Although the process is created VM attach API may not work at early stages of the process run.
|
||||
// "VirtualMachine.list()" call may not list the newly created process which means the process is gone and triggers disconnect.
|
||||
// If lifecycle management is enabled the ready state seem to be a great indicator of a boot process fully started.
|
||||
// TODO: explore health endpoint perhaps instead of ready state under Admin endpoint.
|
||||
// Flux.fromIterable(servers).flatMap(s -> Mono.delay(Duration.ofMillis(500)).then(s.executeCommand(cmd))).subscribe();
|
||||
Mono.delay(Duration.ofMillis(500)).then(LiveProcessCommandsExecutor.getDefault().executeCommand(cmd)).subscribe();
|
||||
CommandInfo cmd = new CommandInfo("sts/livedata/localAdd", Map.of(
|
||||
"host", "127.0.0.1",
|
||||
"port", l.getAttribute(BootLaunchConfigurationDelegate.JMX_PORT),
|
||||
"urlScheme", "http",
|
||||
"jmxurl", "service:jmx:rmi:///jndi/rmi://127.0.0.1:%s/jmxrmi".formatted(l.getAttribute(BootLaunchConfigurationDelegate.JMX_PORT)),
|
||||
"manualConnect", !BootLaunchConfigurationDelegate.getAutoConnect(owner),
|
||||
"processId", pid,
|
||||
"processName", owner.getAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, p.getLabel()),
|
||||
"projectName", project.getProject().getName()
|
||||
));
|
||||
|
||||
LiveProcessCommandsExecutor.getDefault().executeCommand(cmd).subscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case INACTIVE:
|
||||
for (ILaunch l : DebugPlugin.getDefault().getLaunchManager().getLaunches()) {
|
||||
if (l.getLaunchConfiguration() == owner && l.getAttribute(BootLaunchConfigurationDelegate.JMX_PORT) != null) {
|
||||
String jmxUrl = "service:jmx:rmi:///jndi/rmi://127.0.0.1:%s/jmxrmi".formatted(l.getAttribute(BootLaunchConfigurationDelegate.JMX_PORT));
|
||||
LiveProcessCommandsExecutor.getDefault().executeCommand("sts/livedata/localRemove", jmxUrl).subscribe();
|
||||
}
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
getLog().error("Failed to connect to Boot app", e);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019 Pivotal Software, Inc.
|
||||
* Copyright (c) 2019, 2024 Pivotal Software, 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
|
||||
@@ -16,9 +16,9 @@ import java.util.Map.Entry;
|
||||
public class CommandInfo {
|
||||
|
||||
public final String command;
|
||||
public final Map<String,String> info;
|
||||
public final Map<String,?> info;
|
||||
|
||||
public CommandInfo(String command, Map<String, String> info) {
|
||||
public CommandInfo(String command, Map<String, ?> info) {
|
||||
super();
|
||||
this.command = command;
|
||||
this.info = info;
|
||||
@@ -27,7 +27,7 @@ public class CommandInfo {
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder s = new StringBuilder("CommandInfo("+command+", {\n");
|
||||
for (Entry<String, String> e : info.entrySet()) {
|
||||
for (Entry<String, ?> e : info.entrySet()) {
|
||||
s.append(" "+e.getKey() +": "+e.getValue()+"\n");
|
||||
}
|
||||
s.append("})");
|
||||
|
||||
@@ -57,4 +57,12 @@ public final class DefaultLiveProcessCommandExecutor implements LiveProcessComma
|
||||
))).thenAccept(l -> {}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> executeCommand(String command, Object... params) {
|
||||
return Mono.fromFuture(BootLsCommandUtils.getLanguageServers(command).collectAll(ls -> ls.getWorkspaceService().executeCommand(new ExecuteCommandParams(
|
||||
command,
|
||||
ImmutableList.of(params)
|
||||
))).thenAccept(l -> {}));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019, 2023 Pivotal Software, Inc.
|
||||
* Copyright (c) 2019, 2024 Pivotal Software, 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
|
||||
@@ -77,15 +77,24 @@ public class LiveDataConnectionManagementActions extends AbstractDisposable impl
|
||||
public ExecuteCommandAction(CommandInfo commandInfo) {
|
||||
super(params);
|
||||
this.commandInfo = commandInfo;
|
||||
String command = commandInfo.command;
|
||||
int lastSlash = command.lastIndexOf("/");
|
||||
String humanReadableCommand = command.substring(lastSlash+1);
|
||||
humanReadableCommand = humanReadableCommand.substring(0, 1).toUpperCase() + humanReadableCommand.substring(1);
|
||||
this.projectName = commandInfo.info.get("projectName");
|
||||
label = humanReadableCommand + " "+commandInfo.info.get("label");
|
||||
this.projectName = (String) commandInfo.info.get("projectName");
|
||||
label = computeLabel(commandInfo);
|
||||
this.setText(label);
|
||||
}
|
||||
|
||||
private String computeLabel(CommandInfo commandInfo) {
|
||||
switch (commandInfo.command) {
|
||||
case "sts/livedata/connect":
|
||||
return "Show Live Data from: %s".formatted(commandInfo.info.get("label"));
|
||||
case "sts/livedata/refresh":
|
||||
return "Refresh Live Data from: %s".formatted(commandInfo.info.get("label"));
|
||||
case "sts/livedata/disconnect":
|
||||
return "Hide Live Data from: %s".formatted(commandInfo.info.get("label"));
|
||||
default:
|
||||
}
|
||||
return (String) commandInfo.info.get("label");
|
||||
}
|
||||
|
||||
public String getProjectName() {
|
||||
return projectName;
|
||||
}
|
||||
@@ -115,7 +124,7 @@ public class LiveDataConnectionManagementActions extends AbstractDisposable impl
|
||||
}
|
||||
|
||||
public String getProcessId() {
|
||||
return commandInfo.info.get("processId");
|
||||
return (String) commandInfo.info.get("processId");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019, 2023 Pivotal Software, Inc.
|
||||
* Copyright (c) 2019, 2024 Pivotal Software, 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
|
||||
@@ -17,6 +17,7 @@ public interface LiveProcessCommandsExecutor {
|
||||
|
||||
Flux<CommandInfo> listCommands();
|
||||
Mono<Void> executeCommand(CommandInfo cmd);
|
||||
Mono<Void> executeCommand(String command, Object... params);
|
||||
|
||||
static LiveProcessCommandsExecutor getDefault() {
|
||||
return new DefaultLiveProcessCommandExecutor();
|
||||
|
||||
@@ -17,6 +17,7 @@ public class Constants {
|
||||
|
||||
public static final String PLUGIN_ID = "org.springframework.tooling.boot.ls";
|
||||
|
||||
public static final String PREF_LIVE_INFORMATION_ALL_JVM_PROCESSES = "boot-java.live-information.all-local-java-processes";
|
||||
public static final String PREF_LIVE_INFORMATION_FETCH_DATA_RETRY_MAX_NO = "boot-java.live-information.fetch-data.max-retries";
|
||||
public static final String PREF_LIVE_INFORMATION_FETCH_DATA_RETRY_DELAY_IN_SECONDS = "boot-java.live-information.fetch-data.retry-delay-in-seconds";
|
||||
|
||||
|
||||
@@ -192,6 +192,7 @@ public class DelegatingStreamConnectionProvider implements StreamConnectionProvi
|
||||
liveInformationFetchData.put("retry-delay-in-seconds", preferenceStore.getInt(Constants.PREF_LIVE_INFORMATION_FETCH_DATA_RETRY_DELAY_IN_SECONDS));
|
||||
|
||||
liveInformation.put("fetch-data", liveInformationFetchData);
|
||||
liveInformation.put("all-local-java-processes", preferenceStore.getBoolean(Constants.PREF_LIVE_INFORMATION_ALL_JVM_PROCESSES));
|
||||
|
||||
supportXML.put("on", preferenceStore.getBoolean(Constants.PREF_SUPPORT_SPRING_XML_CONFIGS));
|
||||
supportXML.put("scan-folders", preferenceStore.getString(Constants.PREF_XML_CONFIGS_SCAN_FOLDERS));
|
||||
|
||||
@@ -53,13 +53,13 @@ public class LiveProcessCommandElement extends QuickAccessElement {
|
||||
@Override
|
||||
public String getLabel() {
|
||||
if (COMMAND_REFRESH.equals(action)) {
|
||||
return "Refresh Live Data for: " + label;
|
||||
return "Refresh Live Data from: " + label;
|
||||
}
|
||||
else if (COMMAND_CONNECT.equals(action)) {
|
||||
return "Show live data for: " + label;
|
||||
return "Show Live Data from: " + label;
|
||||
}
|
||||
else if (COMMAND_DISCONNECT.equals(action)) {
|
||||
return "Disconnect live data from: " + label;
|
||||
return "Hide Live Data from: " + label;
|
||||
}
|
||||
else {
|
||||
// error case
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2023 VMware, Inc.
|
||||
* Copyright (c) 2023, 2024 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
|
||||
@@ -81,6 +81,7 @@ public class LiveInformationPreferencePage extends FieldEditorPreferencePage imp
|
||||
|
||||
Composite fieldEditorParent = getFieldEditorParent();
|
||||
|
||||
addField(new BooleanFieldEditor(Constants.PREF_LIVE_INFORMATION_ALL_JVM_PROCESSES, "Show all local JVM processes when connecting to Live Spring Boot App processes", fieldEditorParent));
|
||||
addField(new StringFieldEditor(Constants.PREF_LIVE_INFORMATION_FETCH_DATA_RETRY_MAX_NO, "Live Information - Max number of retries (before giving up)", fieldEditorParent));
|
||||
addField(new StringFieldEditor(Constants.PREF_LIVE_INFORMATION_FETCH_DATA_RETRY_DELAY_IN_SECONDS, "Live Information - Delay between retries in seconds", fieldEditorParent));
|
||||
|
||||
|
||||
@@ -62,6 +62,8 @@ public class PrefsInitializer extends AbstractPreferenceInitializer {
|
||||
|
||||
preferenceStore.setDefault(Constants.PREF_MODULITH, true);
|
||||
|
||||
preferenceStore.setDefault(Constants.PREF_LIVE_INFORMATION_ALL_JVM_PROCESSES, false);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2016-2017 Pivotal, Inc.
|
||||
* Copyright (c) 2016-2024 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
|
||||
@@ -19,21 +19,21 @@ public class ShowMessageException extends RuntimeException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public final MessageParams message;
|
||||
|
||||
public ShowMessageException(MessageParams message, Exception cause) {
|
||||
public ShowMessageException(MessageParams message, Throwable cause) {
|
||||
super(message.getMessage(), cause);
|
||||
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public static ShowMessageException error(String message, Exception cause) {
|
||||
public static ShowMessageException error(String message, Throwable cause) {
|
||||
return create(MessageType.Error, message, cause);
|
||||
}
|
||||
|
||||
public static ShowMessageException warning(String message, Exception cause) {
|
||||
public static ShowMessageException warning(String message, Throwable cause) {
|
||||
return create(MessageType.Warning, message, cause);
|
||||
}
|
||||
|
||||
private static ShowMessageException create(MessageType warning, String message, Exception cause) {
|
||||
private static ShowMessageException create(MessageType warning, String message, Throwable cause) {
|
||||
MessageParams m = new MessageParams();
|
||||
|
||||
m.setMessage(message);
|
||||
|
||||
@@ -96,6 +96,11 @@ public class BootJavaConfig implements InitializingBean {
|
||||
return enabled != null && enabled.booleanValue();
|
||||
}
|
||||
|
||||
public boolean isShowingAllJvmProcesses() {
|
||||
Boolean isAll = settings.getBoolean("boot-java", "live-information", "all-local-java-processes");
|
||||
return isAll != null && isAll.booleanValue();
|
||||
}
|
||||
|
||||
public String[] xmlBeansFoldersToScan() {
|
||||
String foldersStr = settings.getString("boot-java", "support-spring-xml-config", "scan-folders");
|
||||
if (foldersStr != null) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018, 2023 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2024 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
|
||||
@@ -21,6 +21,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.lsp4j.CodeActionKind;
|
||||
import org.eclipse.lsp4j.CodeActionOptions;
|
||||
@@ -200,6 +201,33 @@ public class BootLanguageServerBootApp {
|
||||
return bean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
SpringProcessConnectorRemote localAppsFromCommandsConnector(SimpleLanguageServer server, SpringProcessConnectorService liveDataService) {
|
||||
SpringProcessConnectorRemote bean = new SpringProcessConnectorRemote(server, liveDataService);
|
||||
final Map<String, RemoteBootAppData> localApps = new HashMap<>();
|
||||
final Gson gson = new Gson();
|
||||
server.onCommand("sts/livedata/localAdd", params -> {
|
||||
synchronized(localApps) {
|
||||
RemoteBootAppData[] newAdditions = params.getArguments().stream().map(a -> gson.fromJson((JsonElement) a, RemoteBootAppData.class)).toArray(RemoteBootAppData[]::new);
|
||||
for (RemoteBootAppData app : newAdditions) {
|
||||
localApps.put(app.getJmxurl(), app);
|
||||
}
|
||||
bean.updateApps(localApps.values().toArray(new RemoteBootAppData[localApps.size()]));
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
});
|
||||
server.onCommand("sts/livedata/localRemove", params -> {
|
||||
synchronized(localApps) {
|
||||
List<RemoteBootAppData> removedApps = params.getArguments().stream().map(o -> o instanceof JsonElement ? ((JsonElement) o).getAsString() : (String) o).map(localApps::remove).collect(Collectors.toList());
|
||||
if (!removedApps.isEmpty()) {
|
||||
bean.updateApps(localApps.values().toArray(new RemoteBootAppData[localApps.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) 2016, 2023 Pivotal, Inc.
|
||||
* Copyright (c) 2016, 2024 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
|
||||
@@ -143,14 +143,15 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent
|
||||
hoverProvider = createHoverHandler(projectFinder, sourceLinks, liveDataProvider);
|
||||
new SpringProcessLiveHoverUpdater(server, hoverProvider, projectFinder, liveDataProvider);
|
||||
|
||||
BootJavaConfig config = appContext.getBean(BootJavaConfig.class);
|
||||
|
||||
// deal with locally running processes and their connections
|
||||
SpringProcessConnectorLocal liveDataLocalProcessConnector = new SpringProcessConnectorLocal(liveDataService, projectObserver);
|
||||
SpringProcessConnectorLocal liveDataLocalProcessConnector = new SpringProcessConnectorLocal(liveDataService, projectObserver, config);
|
||||
|
||||
// create and handle commands
|
||||
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()));
|
||||
|
||||
//
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019, 2022 Pivotal, Inc.
|
||||
* Copyright (c) 2019, 2024 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
|
||||
@@ -115,20 +115,19 @@ public class SpringProcessCommandHandler {
|
||||
});
|
||||
}
|
||||
|
||||
private CompletableFuture<Object> connect(ExecuteCommandParams params) {
|
||||
private CompletableFuture<?> connect(ExecuteCommandParams params) {
|
||||
String processKey = getProcessKey(params);
|
||||
if (processKey != null) {
|
||||
|
||||
// try local processes
|
||||
if (SpringProcessConnectorLocal.isAvailable()) {
|
||||
if (localProcessConnector.isAvailable()) {
|
||||
|
||||
// Try cached processes.
|
||||
SpringProcessDescriptor[] processes = localProcessConnector
|
||||
.getProcesses(false, SpringProcessStatus.REGULAR, SpringProcessStatus.AUTO_CONNECT);
|
||||
for (SpringProcessDescriptor process : processes) {
|
||||
if (process.getProcessKey().equals(processKey)) {
|
||||
localProcessConnector.connectProcess(process);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
return localProcessConnector.connectProcess(process);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,8 +135,7 @@ public class SpringProcessCommandHandler {
|
||||
.getProcesses(true, SpringProcessStatus.REGULAR, SpringProcessStatus.AUTO_CONNECT);
|
||||
for (SpringProcessDescriptor process : processes) {
|
||||
if (process.getProcessKey().equals(processKey)) {
|
||||
localProcessConnector.connectProcess(process);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
return localProcessConnector.connectProcess(process);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,8 +147,7 @@ public class SpringProcessCommandHandler {
|
||||
for (RemoteBootAppData remoteProcess : remoteProcesses) {
|
||||
String key = SpringProcessConnectorRemote.getProcessKey(remoteProcess);
|
||||
if (processKey.equals(key)) {
|
||||
remoteProcessConnector.connectProcess(remoteProcess);
|
||||
return CompletableFuture.completedFuture(null);
|
||||
return remoteProcessConnector.connectProcess(remoteProcess);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,26 +155,26 @@ public class SpringProcessCommandHandler {
|
||||
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
private CompletableFuture<Object> refresh(ExecuteCommandParams params) {
|
||||
|
||||
private CompletableFuture<?> refresh(ExecuteCommandParams params) {
|
||||
SpringProcessParams springProcessParams = new SpringProcessParams();
|
||||
springProcessParams.setProcessKey(getProcessKey(params));
|
||||
springProcessParams.setEndpoint(getArgumentByKey(params, "endpoint"));
|
||||
if (springProcessParams.getProcessKey() != null) {
|
||||
connectorService.refreshProcess(springProcessParams);
|
||||
return connectorService.refreshProcess(springProcessParams);
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
private CompletableFuture<Object> refreshMetrics(ExecuteCommandParams params) {
|
||||
private CompletableFuture<?> refreshMetrics(ExecuteCommandParams params) {
|
||||
SpringProcessParams springProcessParams = new SpringProcessParams();
|
||||
springProcessParams.setProcessKey(getProcessKey(params));
|
||||
springProcessParams.setEndpoint(getArgumentByKey(params, "endpoint"));
|
||||
springProcessParams.setMetricName(getArgumentByKey(params, "metricName"));
|
||||
springProcessParams.setTags(getArgumentByKey(params, "tags")); // Convert tags to a map
|
||||
if (springProcessParams.getProcessKey() != null) {
|
||||
connectorService.refreshProcess(springProcessParams);
|
||||
return connectorService.refreshProcess(springProcessParams);
|
||||
}
|
||||
|
||||
return CompletableFuture.completedFuture(null);
|
||||
@@ -206,33 +203,54 @@ public class SpringProcessCommandHandler {
|
||||
result.add(new LiveProcessCommand(COMMAND_DISCONNECT, processKey, label, process.getProjectName(), process.getProcessId()));
|
||||
alreadyConnected.add(processKey);
|
||||
}
|
||||
|
||||
|
||||
// collect available remote process. Some of them might be local processes, make a note of these too
|
||||
List<LiveProcessCommand> remoteProcessCommands = new ArrayList<>();
|
||||
Map<String, LiveProcessCommand> localProcessCommands = new HashMap<>();
|
||||
for (SpringProcessConnectorRemote remoteProcessConnector : remoteProcessConnectors) {
|
||||
RemoteBootAppData[] remoteProcesses = remoteProcessConnector.getProcesses();
|
||||
for (RemoteBootAppData remoteProcess : remoteProcesses) {
|
||||
String processKey = SpringProcessConnectorRemote.getProcessKey(remoteProcess);
|
||||
boolean isLocal = remoteProcess.getProcessID() != null && ("localhost".equals(remoteProcess.getHost()) || "127.0.0.1".equals(remoteProcess.getHost()));
|
||||
if (alreadyConnected.contains(processKey)) {
|
||||
alreadyConnected.add(SpringProcessConnectorService.getProcessKey(remoteProcess.getProcessID(), remoteProcess.getProcessName()));
|
||||
} else {
|
||||
String label = createLabel(remoteProcess.getProcessID(), SpringProcessConnectorRemote.getProcessName(remoteProcess));
|
||||
LiveProcessCommand command = new LiveProcessCommand(COMMAND_CONNECT, processKey, label, remoteProcess.getProjectName(), remoteProcess.getProcessID());
|
||||
if (isLocal) {
|
||||
// Local add these later while checking for local boot processes
|
||||
localProcessCommands.put(remoteProcess.getProcessID(), command);
|
||||
} else {
|
||||
// Keep these to be added at the end
|
||||
remoteProcessCommands.add(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// other available local processes
|
||||
if (SpringProcessConnectorLocal.isAvailable()) {
|
||||
if (localProcessConnector.isAvailable()) {
|
||||
SpringProcessDescriptor[] localProcesses = localProcessConnector.getProcesses(true, SpringProcessStatus.REGULAR, SpringProcessStatus.AUTO_CONNECT);
|
||||
for (SpringProcessDescriptor localProcess : localProcesses) {
|
||||
String processKey = localProcess.getProcessKey();
|
||||
if (!alreadyConnected.contains(processKey)) {
|
||||
String label = createLabel(localProcess.getProcessID(), localProcess.getProcessName());
|
||||
|
||||
LiveProcessCommand command = new LiveProcessCommand(COMMAND_CONNECT, processKey, label, localProcess.getProjectName(), null);
|
||||
LiveProcessCommand command = localProcessCommands.remove(localProcess.getProcessID());
|
||||
if (command == null) {
|
||||
String label = createLabel(localProcess.getProcessID(), localProcess.getProcessName());
|
||||
command = new LiveProcessCommand(COMMAND_CONNECT, processKey, label, localProcess.getProjectName(), null);
|
||||
}
|
||||
result.add(command);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// other available remote processes
|
||||
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()));
|
||||
}
|
||||
}
|
||||
for (LiveProcessCommand command : localProcessCommands.values()) {
|
||||
result.add(command);
|
||||
}
|
||||
|
||||
result.addAll(remoteProcessCommands);
|
||||
|
||||
log.debug("getProcessCommands => {}", result);
|
||||
return CompletableFuture.completedFuture((Object[]) result.toArray(new Object[result.size()]));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019, 2022 Pivotal, Inc.
|
||||
* Copyright (c) 2019, 2024 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
|
||||
@@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver;
|
||||
@@ -36,7 +37,6 @@ import com.sun.tools.attach.VirtualMachineDescriptor;
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
@SuppressWarnings("restriction")
|
||||
public class SpringProcessConnectorLocal {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(SpringProcessConnectorLocal.class);
|
||||
@@ -52,8 +52,11 @@ public class SpringProcessConnectorLocal {
|
||||
|
||||
private boolean projectsChanged;
|
||||
|
||||
final private BootJavaConfig config;
|
||||
|
||||
|
||||
public SpringProcessConnectorLocal(SpringProcessConnectorService processConnector, ProjectObserver projectObserver) {
|
||||
public SpringProcessConnectorLocal(SpringProcessConnectorService processConnector, ProjectObserver projectObserver, BootJavaConfig config) {
|
||||
this.config = config;
|
||||
this.projects = new ConcurrentHashMap<>();
|
||||
this.processes = Collections.synchronizedSet(new HashSet<>());
|
||||
this.statusUpdateThreadPool = Executors.newFixedThreadPool(10);
|
||||
@@ -91,14 +94,17 @@ public class SpringProcessConnectorLocal {
|
||||
* checks whether this class can operate normally or not - it is recommended to check this before calling out to this class
|
||||
* (if the attach to VirtualMachine library is not around, this class cannot really do anything and will throw exceptions)
|
||||
*/
|
||||
public static boolean isAvailable() {
|
||||
try {
|
||||
Class<?> vmClass = VirtualMachine.class;
|
||||
return vmClass != null;
|
||||
}
|
||||
catch (NoClassDefFoundError e) {
|
||||
return false;
|
||||
public boolean isAvailable() {
|
||||
if (config.isShowingAllJvmProcesses()) {
|
||||
try {
|
||||
Class<?> vmClass = VirtualMachine.class;
|
||||
return vmClass != null;
|
||||
}
|
||||
catch (NoClassDefFoundError e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isLocalProcess(String processKey) {
|
||||
@@ -186,7 +192,7 @@ public class SpringProcessConnectorLocal {
|
||||
}
|
||||
}
|
||||
|
||||
public void connectProcess(SpringProcessDescriptor descriptor) {
|
||||
public CompletableFuture<Void> connectProcess(SpringProcessDescriptor descriptor) {
|
||||
VirtualMachine vm = null;
|
||||
VirtualMachineDescriptor vmDescriptor = descriptor.getVm();
|
||||
|
||||
@@ -205,6 +211,7 @@ public class SpringProcessConnectorLocal {
|
||||
jmxAddress = vm.startLocalManagementAgent();
|
||||
} catch (Exception e) {
|
||||
log.error("Error starting local management agent", e);
|
||||
return CompletableFuture.failedFuture(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,11 +223,13 @@ public class SpringProcessConnectorLocal {
|
||||
SpringProcessConnectorOverJMX connector = new SpringProcessConnectorOverJMX(ProcessType.LOCAL,
|
||||
descriptor.getProcessKey(), jmxAddress, urlScheme, processID, processName, descriptor.getProjectName(), null, null);
|
||||
|
||||
this.processConnectorService.connectProcess(descriptor.getProcessKey(), connector);
|
||||
return this.processConnectorService.connectProcess(descriptor.getProcessKey(), connector);
|
||||
}
|
||||
return CompletableFuture.failedFuture(new Exception("No JMX URL available!"));
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("exception while connecting to jvm process", e);
|
||||
return CompletableFuture.failedFuture(e);
|
||||
}
|
||||
finally {
|
||||
if (vm != null) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019, 2022 Pivotal, Inc.
|
||||
* Copyright (c) 2019, 2024 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,6 +18,7 @@ import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -43,6 +44,7 @@ public class SpringProcessConnectorRemote {
|
||||
|
||||
private String processId;
|
||||
private String processName;
|
||||
private String projectName;
|
||||
|
||||
public String getJmxurl() {
|
||||
return jmxurl;
|
||||
@@ -100,11 +102,15 @@ public class SpringProcessConnectorRemote {
|
||||
this.processName = processName;
|
||||
}
|
||||
|
||||
public String getProjectName() {
|
||||
return projectName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RemoteBootAppData [jmxurl=" + jmxurl + ", host=" + host + ", urlScheme=" + urlScheme + ", port="
|
||||
+ port + ", manualConnect=" + manualConnect + ", keepChecking=" + keepChecking + ", processId="
|
||||
+ processId + ", processName=" + processName + "]";
|
||||
+ processId + ", processName=" + processName + ". projectName=" + projectName + "]";
|
||||
}
|
||||
|
||||
public void setManualConnection(boolean manualConnect) {
|
||||
@@ -117,7 +123,7 @@ public class SpringProcessConnectorRemote {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(host, jmxurl, keepChecking, manualConnect, port, processId, processName, urlScheme);
|
||||
return Objects.hash(host, jmxurl, keepChecking, manualConnect, port, processId, processName, urlScheme, projectName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,7 +138,8 @@ public class SpringProcessConnectorRemote {
|
||||
return Objects.equals(host, other.host) && Objects.equals(jmxurl, other.jmxurl)
|
||||
&& keepChecking == other.keepChecking && manualConnect == other.manualConnect
|
||||
&& Objects.equals(port, other.port) && Objects.equals(processId, other.processId)
|
||||
&& Objects.equals(processName, other.processName) && Objects.equals(urlScheme, other.urlScheme);
|
||||
&& Objects.equals(processName, other.processName) && Objects.equals(urlScheme, other.urlScheme)
|
||||
&& Objects.equals(projectName, other.getProcessName());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -205,10 +212,10 @@ public class SpringProcessConnectorRemote {
|
||||
}
|
||||
|
||||
public static String getProcessKey(RemoteBootAppData appData) {
|
||||
return "remote process - " + appData.getJmxurl();
|
||||
return appData.getJmxurl();
|
||||
}
|
||||
|
||||
public void connectProcess(RemoteBootAppData remoteProcess) {
|
||||
public CompletableFuture<Void> connectProcess(RemoteBootAppData remoteProcess) {
|
||||
String processKey = getProcessKey(remoteProcess);
|
||||
String processID = remoteProcess.getProcessID();
|
||||
String processName = getProcessName(remoteProcess);
|
||||
@@ -216,14 +223,15 @@ public class SpringProcessConnectorRemote {
|
||||
String host = remoteProcess.getHost();
|
||||
String port = remoteProcess.getPort();
|
||||
String urlScheme = remoteProcess.getUrlScheme();
|
||||
String projectName = remoteProcess.getProjectName();
|
||||
// boolean keepChecking = _appData.isKeepChecking();
|
||||
|
||||
if (jmxURL.startsWith("http")) {
|
||||
SpringProcessConnectorOverHttp connector = new SpringProcessConnectorOverHttp(ProcessType.REMOTE, processKey, jmxURL, urlScheme, processID, processName, urlScheme, host, port);
|
||||
processConnectorService.connectProcess(processKey, connector);
|
||||
SpringProcessConnectorOverHttp connector = new SpringProcessConnectorOverHttp(ProcessType.REMOTE, processKey, jmxURL, urlScheme, processID, processName, projectName, host, port);
|
||||
return processConnectorService.connectProcess(processKey, connector);
|
||||
} else {
|
||||
SpringProcessConnectorOverJMX connector = new SpringProcessConnectorOverJMX(ProcessType.REMOTE, processKey, jmxURL, urlScheme, processID, processName, null, host, port);
|
||||
processConnectorService.connectProcess(processKey, connector);
|
||||
SpringProcessConnectorOverJMX connector = new SpringProcessConnectorOverJMX(ProcessType.REMOTE, processKey, jmxURL, urlScheme, processID, processName, projectName, host, port);
|
||||
return processConnectorService.connectProcess(processKey, connector);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019, 2023 Pivotal, Inc.
|
||||
* Copyright (c) 2019, 2024 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.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
@@ -87,7 +88,7 @@ public class SpringProcessConnectorService {
|
||||
this.retryDelayInSeconds = retryDelayInSeconds;
|
||||
}
|
||||
|
||||
public void connectProcess(String processKey, SpringProcessConnector connector) {
|
||||
public CompletableFuture<Void> connectProcess(String processKey, SpringProcessConnector connector) {
|
||||
log.info("connect to process: " + processKey);
|
||||
|
||||
this.connectors.put(processKey, connector);
|
||||
@@ -95,17 +96,12 @@ public class SpringProcessConnectorService {
|
||||
|
||||
connector.addConnectorChangeListener(connectorListener);
|
||||
|
||||
try {
|
||||
final IndefiniteProgressTask progressTask = getProgressTask(
|
||||
"spring-process-connector-service-connect-" + processKey, "Connect", null);
|
||||
scheduleConnect(progressTask, processKey, connector, 0, TimeUnit.SECONDS, 0);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.error("error connecting to " + processKey, e);
|
||||
}
|
||||
final IndefiniteProgressTask progressTask = getProgressTask(
|
||||
"spring-process-connector-service-connect-" + processKey, "Connect", null);
|
||||
return scheduleConnect(progressTask, processKey, connector, 0, TimeUnit.SECONDS, 0);
|
||||
}
|
||||
|
||||
public void refreshProcess(SpringProcessParams springProcessParams) {
|
||||
public CompletableFuture<Void> refreshProcess(SpringProcessParams springProcessParams) {
|
||||
log.info("refresh process: " + springProcessParams.getProcessKey());
|
||||
|
||||
SpringProcessConnector connector = this.connectors.get(springProcessParams.getProcessKey());
|
||||
@@ -113,8 +109,9 @@ public class SpringProcessConnectorService {
|
||||
final IndefiniteProgressTask progressTask = getProgressTask(
|
||||
"spring-process-connector-service-refresh-data-" + springProcessParams.getProcessKey(), "Refresh", null);
|
||||
|
||||
scheduleRefresh(progressTask, springProcessParams, connector, 0, TimeUnit.SECONDS, 0);
|
||||
return scheduleRefresh(progressTask, springProcessParams, connector, 0, TimeUnit.SECONDS, 0);
|
||||
}
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
|
||||
public SpringProcessLiveData getLiveData(String processKey) {
|
||||
@@ -161,35 +158,35 @@ public class SpringProcessConnectorService {
|
||||
return processID;
|
||||
}
|
||||
|
||||
private void scheduleConnect(IndefiniteProgressTask progressTask, String processKey, SpringProcessConnector connector, long delay, TimeUnit unit, int retryNo) {
|
||||
private CompletableFuture<Void> scheduleConnect(IndefiniteProgressTask progressTask, String processKey, SpringProcessConnector connector, long delay, TimeUnit unit, int retryNo) {
|
||||
String progressMessage = "Connecting to process: " + processKey + " - retry no: " + retryNo;
|
||||
log.info(progressMessage);
|
||||
|
||||
|
||||
this.scheduler.schedule(() -> {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
progressTask.progressEvent(progressMessage);
|
||||
connector.connect();
|
||||
progressTask.done();
|
||||
|
||||
refreshProcess(new SpringProcessParams(processKey, "", "", ""));
|
||||
progressTask.done();
|
||||
}
|
||||
catch (Exception e) {
|
||||
log.info("problem occured during process connect", e);
|
||||
|
||||
if (retryNo < maxRetryCount && isKnownProcessKey(processKey)) {
|
||||
scheduleConnect(progressTask, processKey, connector, retryDelayInSeconds, TimeUnit.SECONDS, retryNo + 1);
|
||||
} else {
|
||||
progressTask.done();
|
||||
|
||||
// Send message to client if maximum retries reached on error
|
||||
if (isKnownProcessKey(processKey)) {
|
||||
diagnosticService.diagnosticEvent(ShowMessageException
|
||||
.error("Failed to connect to process " + processKey + " after retries: " + retryNo, e));
|
||||
}
|
||||
}
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
}, delay, unit);
|
||||
}, CompletableFuture.delayedExecutor(delay, unit, scheduler)).thenCompose(v -> refreshProcess(new SpringProcessParams(processKey, "", "", ""))).exceptionallyCompose(e -> {
|
||||
log.info("problem occured during process connect", e);
|
||||
|
||||
if (retryNo < maxRetryCount && isKnownProcessKey(processKey)) {
|
||||
return scheduleConnect(progressTask, processKey, connector, retryDelayInSeconds, TimeUnit.SECONDS, retryNo + 1);
|
||||
} else {
|
||||
progressTask.done();
|
||||
|
||||
// Send message to client if maximum retries reached on error
|
||||
if (isKnownProcessKey(processKey)) {
|
||||
diagnosticService.diagnosticEvent(ShowMessageException
|
||||
.error("Failed to connect to process " + processKey + " after retries: " + retryNo, e));
|
||||
}
|
||||
return CompletableFuture.completedStage(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void scheduleDisconnect(IndefiniteProgressTask progressTask, String processKey, SpringProcessConnector connector, long delay, TimeUnit unit, int retryNo) {
|
||||
@@ -220,16 +217,14 @@ public class SpringProcessConnectorService {
|
||||
}, delay, unit);
|
||||
}
|
||||
|
||||
private void scheduleRefresh(IndefiniteProgressTask progressTask, SpringProcessParams springProcessParams, SpringProcessConnector connector, long delay, TimeUnit unit, int retryNo) {
|
||||
private CompletableFuture<Void> scheduleRefresh(IndefiniteProgressTask progressTask, SpringProcessParams springProcessParams, SpringProcessConnector connector, long delay, TimeUnit unit, int retryNo) {
|
||||
String processKey = springProcessParams.getProcessKey();
|
||||
String endpoint = springProcessParams.getEndpoint();
|
||||
String metricName = springProcessParams.getMetricName();
|
||||
String progressMessage = "Refreshing data from Spring process: " + processKey + " - retry no: " + retryNo;
|
||||
log.info(progressMessage);
|
||||
|
||||
|
||||
this.scheduler.schedule(() -> {
|
||||
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
progressTask.progressEvent(progressMessage);
|
||||
if(METRICS.equals(endpoint) && (MEMORY.equals(metricName))) {
|
||||
@@ -268,28 +263,32 @@ public class SpringProcessConnectorService {
|
||||
progressTask.done();
|
||||
}
|
||||
catch (Exception e) {
|
||||
|
||||
log.info("problem occured during process live data refresh", e);
|
||||
throw new CompletionException(e);
|
||||
}
|
||||
}, CompletableFuture.delayedExecutor(delay, unit, scheduler)).exceptionallyCompose(e -> {
|
||||
log.info("problem occured during process live data refresh", e);
|
||||
|
||||
if (retryNo < maxRetryCount && isKnownProcessKey(processKey)) {
|
||||
return scheduleRefresh(progressTask, springProcessParams, connector, retryDelayInSeconds, TimeUnit.SECONDS,
|
||||
retryNo + 1);
|
||||
}
|
||||
else {
|
||||
progressTask.done();
|
||||
|
||||
if (retryNo < maxRetryCount && isKnownProcessKey(processKey)) {
|
||||
scheduleRefresh(progressTask, springProcessParams, connector, retryDelayInSeconds, TimeUnit.SECONDS,
|
||||
retryNo + 1);
|
||||
}
|
||||
else {
|
||||
progressTask.done();
|
||||
|
||||
// Send message to client if maximum retries reached on error
|
||||
if (isKnownProcessKey(processKey)) {
|
||||
diagnosticService.diagnosticEvent(ShowMessageException
|
||||
.error("Failed to refresh live data from process " + processKey + " after retries: " + retryNo, e));
|
||||
|
||||
if (!connectedSuccess.containsKey(connector.getProcessKey())) {
|
||||
disconnectProcess(processKey);
|
||||
}
|
||||
// Send message to client if maximum retries reached on error
|
||||
if (isKnownProcessKey(processKey)) {
|
||||
diagnosticService.diagnosticEvent(ShowMessageException
|
||||
.error("Failed to refresh live data from process " + processKey + " after retries: " + retryNo, e));
|
||||
|
||||
if (!connectedSuccess.containsKey(connector.getProcessKey())) {
|
||||
disconnectProcess(processKey);
|
||||
}
|
||||
}
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
}, delay, unit);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private IndefiniteProgressTask getProgressTask(String prefixId, String title, String message) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019 Pivotal, Inc.
|
||||
* Copyright (c) 2019, 2024 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
|
||||
@@ -31,7 +31,6 @@ public class SpringProcessTracker {
|
||||
private static final Logger log = LoggerFactory.getLogger(SpringProcessTracker.class);
|
||||
|
||||
private final SpringProcessConnectorLocal localProcessConnector;
|
||||
private final boolean isConnectorAvailable;
|
||||
|
||||
private boolean automaticTrackingEnabled;
|
||||
private Duration POLLING_INTERVAL;
|
||||
@@ -43,9 +42,7 @@ public class SpringProcessTracker {
|
||||
this.localProcessConnector = localProcessConnector;
|
||||
this.POLLING_INTERVAL = pollingInterval != null ? pollingInterval : Duration.ofMillis(BootJavaConfig.LIVE_INFORMATION_AUTOMATIC_TRACKING_DELAY_DEFAULT);
|
||||
this.automaticTrackingEnabled = false;
|
||||
this.processesAlreadySeen = new HashSet<>();
|
||||
|
||||
this.isConnectorAvailable = SpringProcessConnectorLocal.isAvailable();
|
||||
this.processesAlreadySeen = new HashSet<>();
|
||||
}
|
||||
|
||||
public synchronized void setTrackingEnabled(boolean trackingEnabled) {
|
||||
@@ -74,8 +71,8 @@ public class SpringProcessTracker {
|
||||
}
|
||||
|
||||
public synchronized void start() {
|
||||
if (!isConnectorAvailable) {
|
||||
log.error("virtual machine connector library not available, no automatic local process tracking possible");
|
||||
if (!localProcessConnector.isAvailable()) {
|
||||
log.error("No automatic local process tracking possible");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 512.008 512.008" xml:space="preserve" fill="#000000"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <g> <g> <path style="fill:#868491;" d="M280.01,464.008h-48c-4.422,0-8,3.578-8,8v8c0,17.648,14.352,32,32,32s32-14.352,32-32v-8 C288.01,467.586,284.432,464.008,280.01,464.008z"></path> </g> <g> <path style="fill:#5C546A;" d="M256.002,464.008H232.01c-4.422,0-8,3.578-8,8v8c0,17.646,14.347,31.995,31.992,31.999 L256.002,464.008L256.002,464.008z"></path> </g> <g> <path style="fill:#B4B6BC;" d="M381.983,49.895c-37.391-35.141-86.367-52.742-137.734-49.52 C150.639,6.2,75.014,84.286,72.092,178.149c-2.156,69.547,34.375,133.578,95.344,167.117c15.156,8.344,24.57,24.367,24.57,41.828 v60.914c0,17.648,14.352,32,32,32h64c17.648,0,32-14.352,32-32v-60.914c0-17.445,9.484-33.508,24.766-41.93 c58.742-32.406,95.234-94.156,95.234-161.156C440.006,133.43,418.858,84.547,381.983,49.895z"></path> </g> <g> <circle style="fill:#FFFFFF;" cx="256.002" cy="184.008" r="128"></circle> </g> <g> <g> <path style="fill:#868491;" d="M283.986,232.008c-10.633,0-16.07-6.203-20.031-10.727c-3.492-3.984-4.883-5.273-8-5.273 c-3.102,0-4.484,1.289-7.961,5.266c-3.734,4.273-9.383,10.734-20.016,10.734c-10.625,0-16.273-6.461-20.008-10.734 c-3.477-3.977-4.859-5.266-7.961-5.266c-4.422,0-8-3.578-8-8s3.578-8,8-8c10.625,0,16.273,6.461,20.008,10.734 c3.477,3.977,4.859,5.266,7.961,5.266s4.492-1.289,7.969-5.266c3.734-4.273,9.383-10.734,20.008-10.734 c10.633,0,16.07,6.203,20.031,10.727c3.492,3.984,4.883,5.273,8,5.273c3.109,0,4.5-1.289,7.992-5.266 c3.742-4.273,9.391-10.734,20.031-10.734c4.422,0,8,3.578,8,8s-3.578,8-8,8c-3.117,0-4.508,1.289-8,5.273 C300.267,225.555,294.619,232.008,283.986,232.008z"></path> </g> </g> <g> <g> <path style="fill:#5C546A;" d="M232.002,384.008c-3.68,0-6.992-2.555-7.813-6.297l-40-184c-0.938-4.32,1.805-8.578,6.117-9.523 c4.344-0.953,8.578,1.805,9.523,6.117l40,184c0.938,4.32-1.805,8.578-6.117,9.523 C233.135,383.954,232.564,384.008,232.002,384.008z"></path> </g> </g> <g> <g> <path style="fill:#5C546A;" d="M280.017,384.008c-0.563,0-1.133-0.055-1.711-0.18c-4.313-0.945-7.055-5.203-6.117-9.523l40-184 c0.945-4.313,5.164-7.063,9.523-6.117c4.313,0.945,7.055,5.203,6.117,9.523l-40,184 C287.01,381.454,283.697,384.008,280.017,384.008z"></path> </g> </g> <g> <path style="fill:#B4B6BC;" d="M190.633,376.008c0.851,3.593,1.374,7.293,1.374,11.086v60.914c0,17.648,14.352,32,32,32h64 c17.648,0,32-14.352,32-32v-60.914c0-3.79,0.527-7.491,1.384-11.086H190.633z"></path> </g> <g> <path style="fill:#868491;" d="M256.002,376.008h-65.369c0.851,3.593,1.374,7.293,1.374,11.086v60.914c0,17.648,14.352,32,32,32 h31.995V376.008z"></path> </g> <g> <g> <rect x="256.002" y="400.008" style="fill:#868491;" width="64" height="16"></rect> </g> </g> <g> <g> <rect x="256.006" y="432.008" style="fill:#868491;" width="64" height="16"></rect> </g> </g> <g> <g> <rect x="192.002" y="400.008" style="fill:#5C546A;" width="64" height="16"></rect> </g> </g> <g> <g> <rect x="192.002" y="432.008" style="fill:#5C546A;" width="64" height="16"></rect> </g> </g> </g> </g></svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -0,0 +1,77 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
viewBox="0 0 512.008 512.008" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#868491;" d="M280.01,464.008h-48c-4.422,0-8,3.578-8,8v8c0,17.648,14.352,32,32,32s32-14.352,32-32v-8
|
||||
C288.01,467.586,284.432,464.008,280.01,464.008z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#5C546A;" d="M256.002,464.008H232.01c-4.422,0-8,3.578-8,8v8c0,17.646,14.347,31.995,31.992,31.999
|
||||
L256.002,464.008L256.002,464.008z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#FFCD00;" d="M381.983,49.895c-37.391-35.141-86.367-52.742-137.734-49.52
|
||||
C150.639,6.2,75.014,84.286,72.092,178.149c-2.156,69.547,34.375,133.578,95.344,167.117c15.156,8.344,24.57,24.367,24.57,41.828
|
||||
v60.914c0,17.648,14.352,32,32,32h64c17.648,0,32-14.352,32-32v-60.914c0-17.445,9.484-33.508,24.766-41.93
|
||||
c58.742-32.406,95.234-94.156,95.234-161.156C440.006,133.43,418.858,84.547,381.983,49.895z"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle style="fill:#FFE671;" cx="256.002" cy="184.008" r="128"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#FFFFFF;" d="M283.986,232.008c-10.633,0-16.07-6.203-20.031-10.727c-3.492-3.984-4.883-5.273-8-5.273
|
||||
c-3.102,0-4.484,1.289-7.961,5.266c-3.734,4.273-9.383,10.734-20.016,10.734c-10.625,0-16.273-6.461-20.008-10.734
|
||||
c-3.477-3.977-4.859-5.266-7.961-5.266c-4.422,0-8-3.578-8-8s3.578-8,8-8c10.625,0,16.273,6.461,20.008,10.734
|
||||
c3.477,3.977,4.859,5.266,7.961,5.266s4.492-1.289,7.969-5.266c3.734-4.273,9.383-10.734,20.008-10.734
|
||||
c10.633,0,16.07,6.203,20.031,10.727c3.492,3.984,4.883,5.273,8,5.273c3.109,0,4.5-1.289,7.992-5.266
|
||||
c3.742-4.273,9.391-10.734,20.031-10.734c4.422,0,8,3.578,8,8s-3.578,8-8,8c-3.117,0-4.508,1.289-8,5.273
|
||||
C300.267,225.555,294.619,232.008,283.986,232.008z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#5C546A;" d="M232.002,384.008c-3.68,0-6.992-2.555-7.813-6.297l-40-184c-0.938-4.32,1.805-8.578,6.117-9.523
|
||||
c4.344-0.953,8.578,1.805,9.523,6.117l40,184c0.938,4.32-1.805,8.578-6.117,9.523
|
||||
C233.135,383.954,232.564,384.008,232.002,384.008z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path style="fill:#5C546A;" d="M280.017,384.008c-0.563,0-1.133-0.055-1.711-0.18c-4.313-0.945-7.055-5.203-6.117-9.523l40-184
|
||||
c0.945-4.313,5.164-7.063,9.523-6.117c4.313,0.945,7.055,5.203,6.117,9.523l-40,184
|
||||
C287.01,381.454,283.697,384.008,280.017,384.008z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#B4B6BC;" d="M190.633,376.008c0.851,3.593,1.374,7.293,1.374,11.086v60.914c0,17.648,14.352,32,32,32h64
|
||||
c17.648,0,32-14.352,32-32v-60.914c0-3.79,0.527-7.491,1.384-11.086H190.633z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path style="fill:#868491;" d="M256.002,376.008h-65.369c0.851,3.593,1.374,7.293,1.374,11.086v60.914c0,17.648,14.352,32,32,32
|
||||
h31.995V376.008z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="256.002" y="400.008" style="fill:#868491;" width="64" height="16"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="256.006" y="432.008" style="fill:#868491;" width="64" height="16"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="192.002" y="400.008" style="fill:#5C546A;" width="64" height="16"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<rect x="192.002" y="432.008" style="fill:#5C546A;" width="64" height="16"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
@@ -7,18 +7,23 @@ import { debug,
|
||||
DebugConfigurationProviderTriggerKind,
|
||||
DebugSessionCustomEvent,
|
||||
Disposable,
|
||||
ProviderResult
|
||||
workspace,
|
||||
DebugSession
|
||||
} from "vscode";
|
||||
import * as path from "path";
|
||||
import psList from 'ps-list';
|
||||
import { ListenablePreferenceSetting } from "@pivotal-tools/commons-vscode/lib/launch-util";
|
||||
import { getPortPromise } from "portfinder";
|
||||
import { RemoteBootApp } from "./live-hover-connect-ui";
|
||||
|
||||
const JMX_VM_ARG = '-Dspring.jmx.enabled='
|
||||
const ACTUATOR_JMX_EXPOSURE_ARG = '-Dmanagement.endpoints.jmx.exposure.include='
|
||||
const ADMIN_VM_ARG = '-Dspring.application.admin.enabled='
|
||||
const JMX_VM_ARG = '-Dspring.jmx.enabled=';
|
||||
const ACTUATOR_JMX_EXPOSURE_ARG = '-Dmanagement.endpoints.jmx.exposure.include=';
|
||||
const ADMIN_VM_ARG = '-Dspring.application.admin.enabled=';
|
||||
const BOOT_PROJECT_ARG = '-Dspring.boot.project.name=';
|
||||
const RMI_HOSTNAME = '-Djava.rmi.server.hostname=localhost';
|
||||
|
||||
const JMX_PORT_ARG = '-Dcom.sun.management.jmxremote.port=';
|
||||
|
||||
export const TEST_RUNNER_MAIN_CLASSES = [
|
||||
'org.eclipse.jdt.internal.junit.runner.RemoteTestRunner',
|
||||
'com.microsoft.java.test.runner.Launcher'
|
||||
@@ -30,12 +35,13 @@ interface ProcessEvent {
|
||||
shellProcessId: number
|
||||
}
|
||||
|
||||
|
||||
class SpringBootDebugConfigProvider implements DebugConfigurationProvider {
|
||||
|
||||
resolveDebugConfigurationWithSubstitutedVariables(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken): ProviderResult<DebugConfiguration> {
|
||||
async resolveDebugConfigurationWithSubstitutedVariables(folder: WorkspaceFolder | undefined, debugConfiguration: DebugConfiguration, token?: CancellationToken) {
|
||||
// Running app live hovers support
|
||||
if (!TEST_RUNNER_MAIN_CLASSES.includes(debugConfiguration.mainClass) && isActuatorOnClasspath(debugConfiguration)) {
|
||||
if (debugConfiguration.vmArgs) {
|
||||
if (typeof debugConfiguration.vmArgs === 'string') {
|
||||
if (debugConfiguration.vmArgs.indexOf(JMX_VM_ARG) < 0) {
|
||||
debugConfiguration.vmArgs += ` ${JMX_VM_ARG}true`;
|
||||
}
|
||||
@@ -51,8 +57,15 @@ class SpringBootDebugConfigProvider implements DebugConfigurationProvider {
|
||||
if (debugConfiguration.vmArgs.indexOf(RMI_HOSTNAME) < 0) {
|
||||
debugConfiguration.vmArgs += ` ${RMI_HOSTNAME}`;
|
||||
}
|
||||
if (debugConfiguration.vmArgs.indexOf(JMX_PORT_ARG) < 0) {
|
||||
debugConfiguration.vmArgs += ` ${JMX_PORT_ARG}${await getPortPromise({
|
||||
startPort: 10000
|
||||
})} -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false`
|
||||
}
|
||||
} else {
|
||||
debugConfiguration.vmArgs = `${JMX_VM_ARG}true ${ACTUATOR_JMX_EXPOSURE_ARG}* ${ADMIN_VM_ARG}true ${BOOT_PROJECT_ARG}${debugConfiguration.projectName} ${RMI_HOSTNAME}`;
|
||||
debugConfiguration.vmArgs = `${JMX_VM_ARG}true ${ACTUATOR_JMX_EXPOSURE_ARG}* ${ADMIN_VM_ARG}true ${BOOT_PROJECT_ARG}${debugConfiguration.projectName} ${RMI_HOSTNAME} ${JMX_PORT_ARG}${await getPortPromise({
|
||||
startPort: 10000
|
||||
})} -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false`;
|
||||
}
|
||||
}
|
||||
return debugConfiguration;
|
||||
@@ -87,12 +100,13 @@ export function hookListenerToBooleanPreference(setting: string, listenerCreator
|
||||
}
|
||||
|
||||
export function startDebugSupport(): Disposable {
|
||||
return hookListenerToBooleanPreference(
|
||||
'boot-java.live-information.automatic-connection.on',
|
||||
() => Disposable.from(
|
||||
debug.onDidReceiveDebugSessionCustomEvent(handleCustomDebugEvent),
|
||||
debug.registerDebugConfigurationProvider('java', new SpringBootDebugConfigProvider(), DebugConfigurationProviderTriggerKind.Initial)
|
||||
)
|
||||
return Disposable.from(
|
||||
hookListenerToBooleanPreference(
|
||||
'boot-java.live-information.automatic-connection.on',
|
||||
() => debug.registerDebugConfigurationProvider('java', new SpringBootDebugConfigProvider(), DebugConfigurationProviderTriggerKind.Initial)
|
||||
),
|
||||
debug.onDidReceiveDebugSessionCustomEvent(handleCustomDebugEvent),
|
||||
debug.onDidTerminateDebugSession(handleTerminateDebugSession)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -102,13 +116,34 @@ async function handleCustomDebugEvent(e: DebugSessionCustomEvent): Promise<void>
|
||||
if (canConnect(debugConfiguration)) {
|
||||
setTimeout(async () => {
|
||||
const pid = await getAppPid(e.body as ProcessEvent);
|
||||
const processKey = pid.toString();
|
||||
commands.executeCommand('sts/livedata/connect', { processKey });
|
||||
const vmArgs = debugConfiguration.vmArgs as string;
|
||||
let idx = vmArgs.indexOf(JMX_PORT_ARG) + JMX_PORT_ARG.length;
|
||||
let jmxPort = "";
|
||||
for (; idx < vmArgs.length && vmArgs.charAt(idx) <= "9" && vmArgs.charAt(idx) >= "0"; idx++) {
|
||||
jmxPort += vmArgs.charAt(idx);
|
||||
}
|
||||
await commands.executeCommand("vscode-spring-boot.live.activate", {
|
||||
host: "127.0.0.1",
|
||||
port: null,
|
||||
urlScheme: "http",
|
||||
jmxurl: `service:jmx:rmi:///jndi/rmi://127.0.0.1:${jmxPort}/jmxrmi`,
|
||||
manualConnect: true,
|
||||
processId: pid.toString(),
|
||||
processName: debugConfiguration.mainClass,
|
||||
projectName: debugConfiguration.projectName
|
||||
} as RemoteBootApp);
|
||||
if (workspace.getConfiguration("boot-java.live-information.automatic-connection").get("on")) {
|
||||
await commands.executeCommand("vscode-spring-boot.live.show.active");
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleTerminateDebugSession(session: DebugSession) {
|
||||
commands.executeCommand('vscode-spring-boot.live.deactivate');
|
||||
}
|
||||
|
||||
async function getAppPid(e: ProcessEvent): Promise<number> {
|
||||
if (e.processId && e.processId > 0) {
|
||||
return e.processId;
|
||||
@@ -141,8 +176,9 @@ function isActuatorJarFile(f: string): boolean {
|
||||
|
||||
function canConnect(debugConfiguration: DebugConfiguration): boolean {
|
||||
if (!TEST_RUNNER_MAIN_CLASSES.includes(debugConfiguration.mainClass) && isActuatorOnClasspath(debugConfiguration)) {
|
||||
return debugConfiguration.vmArgs
|
||||
return typeof debugConfiguration.vmArgs === 'string'
|
||||
&& debugConfiguration.vmArgs.indexOf(`${JMX_VM_ARG}true`) >= 0
|
||||
&& debugConfiguration.vmArgs.indexOf(JMX_PORT_ARG) >= 0
|
||||
&& debugConfiguration.vmArgs.indexOf(`${ADMIN_VM_ARG}true`) >= 0
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
import * as VSCode from 'vscode';
|
||||
import { LanguageClient } from "vscode-languageclient/node";
|
||||
import { ActivatorOptions } from '@pivotal-tools/commons-vscode';
|
||||
import { commands, window } from 'vscode';
|
||||
|
||||
interface ProcessCommandInfo {
|
||||
processKey : string;
|
||||
@@ -11,30 +10,74 @@ interface ProcessCommandInfo {
|
||||
projectName: string;
|
||||
}
|
||||
|
||||
export interface RemoteBootApp {
|
||||
jmxurl: string;
|
||||
host: string;
|
||||
urlScheme: "https" | "http";
|
||||
port: string;
|
||||
manualConnect: boolean;
|
||||
keepChecking?: boolean;
|
||||
processId: string;
|
||||
processName: string;
|
||||
projectName?: string;
|
||||
}
|
||||
|
||||
type BootAppState = "none" | "connecting" | "connected" | "disconnecting" | "disconnected";
|
||||
|
||||
let activeBootApp: RemoteBootApp | undefined;
|
||||
let state: BootAppState
|
||||
|
||||
async function liveHoverConnectHandler() {
|
||||
//sts.vscode-spring-boot.codeAction
|
||||
const processData : ProcessCommandInfo[] = await VSCode.commands.executeCommand('sts/livedata/listProcesses');
|
||||
const choiceMap = new Map<string, ProcessCommandInfo>();
|
||||
const choices : string[] = [];
|
||||
processData.forEach(p => {
|
||||
const slash = p.action.lastIndexOf('/');
|
||||
if (slash>=0) {
|
||||
var actionLabel = p.action.substring(slash+1);
|
||||
actionLabel = actionLabel.substring(0, 1).toUpperCase() + actionLabel.substring(1);
|
||||
const choiceLabel = actionLabel + " " + p.label;
|
||||
choiceMap.set(choiceLabel, p);
|
||||
choices.push(choiceLabel);
|
||||
let actionLabel = "";
|
||||
switch (p.action) {
|
||||
case "sts/livedata/connect":
|
||||
actionLabel = "Show"
|
||||
break;
|
||||
case "sts/livedata/refresh":
|
||||
actionLabel = "Refresh";
|
||||
break;
|
||||
case "sts/livedata/disconnect":
|
||||
actionLabel = "Hide";
|
||||
break;
|
||||
}
|
||||
const choiceLabel = actionLabel + " Live Data from: " + p.label;
|
||||
choiceMap.set(choiceLabel, p);
|
||||
choices.push(choiceLabel);
|
||||
});
|
||||
if (choices) {
|
||||
const picked = await VSCode.window.showQuickPick(choices);
|
||||
if (picked) {
|
||||
const chosen = choiceMap.get(picked);
|
||||
await VSCode.commands.executeCommand(chosen.action, chosen);
|
||||
if (activeBootApp?.jmxurl === chosen.processKey) {
|
||||
switch (chosen.action) {
|
||||
case "sts/livedata/connect":
|
||||
await commands.executeCommand('vscode-spring-boot.live.show.active');
|
||||
break;
|
||||
case "sts/livedata/disconnect":
|
||||
await commands.executeCommand('vscode-spring-boot.live.hide.active');
|
||||
break;
|
||||
default:
|
||||
await VSCode.commands.executeCommand(chosen.action, chosen);
|
||||
}
|
||||
} else {
|
||||
await VSCode.commands.executeCommand(chosen.action, chosen);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function updateBootAppState(newState: BootAppState) {
|
||||
if (newState !== state) {
|
||||
state = newState;
|
||||
commands.executeCommand('setContext', 'vscode-spring-boot.active-app-state', state);
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when extension is activated */
|
||||
export function activate(
|
||||
client: LanguageClient,
|
||||
@@ -42,12 +85,58 @@ export function activate(
|
||||
context: VSCode.ExtensionContext
|
||||
) {
|
||||
context.subscriptions.push(
|
||||
VSCode.commands.registerCommand('vscode-spring-boot.live-hover.connect', () => {
|
||||
|
||||
commands.registerCommand('vscode-spring-boot.live-hover.connect', () => {
|
||||
if (client.isRunning()) {
|
||||
return liveHoverConnectHandler();
|
||||
} else {
|
||||
VSCode.window.showErrorMessage("No Spring Boot project found. Action is only available for Spring Boot Projects");
|
||||
window.showErrorMessage("No Spring Boot project found. Action is only available for Spring Boot Projects");
|
||||
}
|
||||
})
|
||||
}),
|
||||
|
||||
commands.registerCommand("vscode-spring-boot.live.activate", async appData => {
|
||||
activeBootApp = appData;
|
||||
await commands.executeCommand('sts/livedata/localAdd', activeBootApp);
|
||||
updateBootAppState("disconnected");
|
||||
}),
|
||||
|
||||
commands.registerCommand("vscode-spring-boot.live.deactivate", async () => {
|
||||
await commands.executeCommand('sts/livedata/localRemove', activeBootApp.jmxurl);
|
||||
activeBootApp = undefined;
|
||||
updateBootAppState("none");
|
||||
}),
|
||||
|
||||
commands.registerCommand("vscode-spring-boot.live.show.active", async () => {
|
||||
try {
|
||||
updateBootAppState("connecting");
|
||||
await commands.executeCommand('sts/livedata/connect', {
|
||||
processKey: activeBootApp.jmxurl
|
||||
});
|
||||
updateBootAppState("connected");
|
||||
} catch (error) {
|
||||
updateBootAppState("disconnected");
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
|
||||
commands.registerCommand("vscode-spring-boot.live.refresh.active", async () => {
|
||||
await commands.executeCommand('sts/livedata/refresh', {
|
||||
processKey: activeBootApp.jmxurl
|
||||
});
|
||||
}),
|
||||
|
||||
commands.registerCommand("vscode-spring-boot.live.hide.active", async () => {
|
||||
try {
|
||||
updateBootAppState("disconnecting");
|
||||
await commands.executeCommand('sts/livedata/disconnect', {
|
||||
processKey: activeBootApp.jmxurl
|
||||
});
|
||||
updateBootAppState("disconnected");
|
||||
} catch (error) {
|
||||
updateBootAppState("connected");
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
@@ -124,13 +124,42 @@
|
||||
{
|
||||
"command": "vscode-spring-boot.yaml-to-props",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "vscode-spring-boot.live.show.active",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "vscode-spring-boot.live.hide.active",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "vscode-spring-boot.live.refresh.active",
|
||||
"when": "false"
|
||||
}
|
||||
],
|
||||
"debug/toolBar": [
|
||||
{
|
||||
"when": "inDebugMode && debugType == 'java' && (vscode-spring-boot.active-app-state == 'disconnected' || vscode-spring-boot.active-app-state == 'connecting')",
|
||||
"command": "vscode-spring-boot.live.show.active",
|
||||
"group": "Spring Boot"
|
||||
},
|
||||
{
|
||||
"when": "inDebugMode && debugType == 'java' && vscode-spring-boot.active-app-state == 'connected'",
|
||||
"command": "vscode-spring-boot.live.refresh.active",
|
||||
"group": "Spring Boot"
|
||||
},
|
||||
{
|
||||
"when": "inDebugMode && debugType == 'java' && (vscode-spring-boot.active-app-state == 'connected' || vscode-spring-boot.active-app-state == 'disconnecting')",
|
||||
"command": "vscode-spring-boot.live.hide.active",
|
||||
"group": "Spring Boot"
|
||||
}
|
||||
]
|
||||
},
|
||||
"commands": [
|
||||
{
|
||||
"command": "vscode-spring-boot.live-hover.connect",
|
||||
"title": "Manage Live Spring Boot Process Connections",
|
||||
"title": "Show/Refresh/Hide Live Data from Spring Boot Processes",
|
||||
"category": "Spring Boot"
|
||||
},
|
||||
{
|
||||
@@ -180,6 +209,27 @@
|
||||
"command": "vscode-spring-boot.yaml-to-props",
|
||||
"title": "Convert .yaml to .properties",
|
||||
"category": "Spring Boot"
|
||||
},
|
||||
{
|
||||
"command": "vscode-spring-boot.live.show.active",
|
||||
"title": "Show Live Data from launched Spring Boot App",
|
||||
"enablement": "vscode-spring-boot.active-app-state == 'disconnected'",
|
||||
"icon": "./images/light-bulb-off.svg",
|
||||
"category": "Spring Boot"
|
||||
},
|
||||
{
|
||||
"command": "vscode-spring-boot.live.hide.active",
|
||||
"title": "Hide Live Data from launched Spring Boot App",
|
||||
"enablement": "vscode-spring-boot.active-app-state == 'connected'",
|
||||
"icon": "./images/light-bulb-on.svg",
|
||||
"category": "Spring Boot"
|
||||
},
|
||||
{
|
||||
"command": "vscode-spring-boot.live.refresh.active",
|
||||
"title": "Refresh Live Data from launched Spring Boot App",
|
||||
"enablement": "vscode-spring-boot.active-app-state == 'connected'",
|
||||
"icon": "$(refresh)",
|
||||
"category": "Spring Boot"
|
||||
}
|
||||
],
|
||||
"configuration": [
|
||||
@@ -198,6 +248,11 @@
|
||||
"default": true,
|
||||
"description": "Live Information - Automatic addition of JVM arguments enabling JMX and Process Connection via JMX Enabled"
|
||||
},
|
||||
"boot-java.live-information.all-local-java-processes": {
|
||||
"type": "boolean",
|
||||
"description": "Show all local JVM processes when connecting to Live Spring Boot App processes",
|
||||
"default": false
|
||||
},
|
||||
"boot-java.live-information.fetch-data.max-retries": {
|
||||
"type": "number",
|
||||
"default": 10,
|
||||
@@ -1099,6 +1154,7 @@
|
||||
"vsce-pre-release-package": "vsce package --pre-release"
|
||||
},
|
||||
"dependencies": {
|
||||
"portfinder": "1.0.32",
|
||||
"@pivotal-tools/commons-vscode": "file:../commons-vscode/pivotal-tools-commons-vscode-0.2.4.tgz",
|
||||
"ps-list": "^7.2.0",
|
||||
"vscode-languageclient": "^9.0.1"
|
||||
|
||||
Reference in New Issue
Block a user