initial steps to support live bean hovers for non-boot projects
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2019 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
|
||||
@@ -17,6 +17,7 @@ import java.lang.management.PlatformManagedObject;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -30,6 +31,8 @@ import java.util.concurrent.TimeUnit;
|
||||
import javax.management.InstanceNotFoundException;
|
||||
import javax.management.MBeanServerConnection;
|
||||
import javax.management.ObjectName;
|
||||
import javax.management.Query;
|
||||
import javax.management.QueryExp;
|
||||
import javax.management.remote.JMXConnector;
|
||||
import javax.management.remote.JMXConnectorFactory;
|
||||
import javax.management.remote.JMXServiceURL;
|
||||
@@ -74,6 +77,8 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
|
||||
|
||||
private String jmxMbeanActuatorDomain;
|
||||
|
||||
private Set<ObjectName> nonBootLiveMBeanNames;
|
||||
|
||||
private LiveBeansModel cachedBeansModel;
|
||||
private String cachedBeansModelMD5;
|
||||
|
||||
@@ -103,6 +108,9 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
|
||||
@Override
|
||||
public abstract boolean isSpringBootApp();
|
||||
|
||||
@Override
|
||||
public abstract boolean isSpringApp();
|
||||
|
||||
|
||||
private final MemoizingDisposableSupplier<JMXConnector> jmxConnector = new MemoizingDisposableSupplier<JMXConnector>(
|
||||
//creating jmx connector:
|
||||
@@ -219,6 +227,28 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean providesNonBootLiveBeans() {
|
||||
return getNonBootSpringLiveMBeans().size() > 0;
|
||||
}
|
||||
|
||||
protected Set<ObjectName> getNonBootSpringLiveMBeans() {
|
||||
if (this.nonBootLiveMBeanNames == null) {
|
||||
try {
|
||||
this.nonBootLiveMBeanNames = withJmxConnector(jmxConnector -> {
|
||||
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
|
||||
QueryExp queryExp = Query.isInstanceOf(Query.value("org.springframework.context.support.LiveBeansView"));
|
||||
return connection.queryNames(null, queryExp);
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
this.nonBootLiveMBeanNames = Collections.emptySet();
|
||||
}
|
||||
}
|
||||
|
||||
return this.nonBootLiveMBeanNames;
|
||||
}
|
||||
|
||||
protected ObjectName getObjectName(String keyProperties) throws Exception {
|
||||
String domain = getDomainForActuator();
|
||||
return getObjectName(domain, keyProperties);
|
||||
@@ -240,6 +270,10 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
|
||||
try {
|
||||
String domain = getDomainForActuator();
|
||||
json = getBeansFromActuator(domain);
|
||||
|
||||
if (json == null && this.providesNonBootLiveBeans()) {
|
||||
json = getBeansFromNonBootMBean();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// PT 160096886 - Don't throw exception, as actuator info will not be available when app stopping, and
|
||||
// this is not an error condition. Return empty model instead.
|
||||
@@ -253,7 +287,14 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
|
||||
|
||||
synchronized(AbstractSpringBootApp.this) {
|
||||
if (cachedBeansModel == null || !md5.equals(cachedBeansModelMD5)) {
|
||||
cachedBeansModel = LiveBeansModel.parse(gson.toJson(json));
|
||||
|
||||
if (json instanceof String) {
|
||||
cachedBeansModel = LiveBeansModel.parse((String)json);
|
||||
}
|
||||
else {
|
||||
cachedBeansModel = LiveBeansModel.parse(gson.toJson(json));
|
||||
}
|
||||
|
||||
cachedBeansModelMD5 = md5;
|
||||
logger.debug("Got {} beans for {}", cachedBeansModel.getBeanNames().size(), this);
|
||||
} else {
|
||||
@@ -276,6 +317,16 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
|
||||
}
|
||||
}
|
||||
|
||||
protected Object getBeansFromNonBootMBean() throws Exception {
|
||||
Set<ObjectName> nonBootSpringLiveMBeans = getNonBootSpringLiveMBeans();
|
||||
if (nonBootSpringLiveMBeans.size() > 0) {
|
||||
return getActuatorDataFromAttribute(nonBootSpringLiveMBeans.iterator().next(), "SnapshotAsJson");
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Object getBeansFromActuator(String domain) throws Exception {
|
||||
Object result = getActuatorDataFromOperation(getObjectName(domain, "type=Endpoint,name=Beans"), "beans");
|
||||
if (result != null) return result;
|
||||
@@ -342,7 +393,7 @@ public abstract class AbstractSpringBootApp implements SpringBootApp {
|
||||
return withJmxConnector(jmxConnector -> {
|
||||
try {
|
||||
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
|
||||
return connection.getAttribute(objectName, "Data");
|
||||
return connection.getAttribute(objectName, attribute);
|
||||
} catch (InstanceNotFoundException|IOException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* Copyright (c) 2017, 2019 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,11 +11,9 @@
|
||||
package org.springframework.ide.vscode.commons.boot.app.cli;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -29,6 +27,7 @@ import com.sun.tools.attach.VirtualMachineDescriptor;
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
@SuppressWarnings("restriction")
|
||||
public class LocalSpringBootApp extends AbstractSpringBootApp {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(LocalSpringBootApp.class);
|
||||
@@ -39,6 +38,7 @@ public class LocalSpringBootApp extends AbstractSpringBootApp {
|
||||
private static final String LOCAL_CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress";
|
||||
|
||||
private Boolean isSpringBootApp;
|
||||
private Boolean isSpringApp;
|
||||
|
||||
private static LocalSpringBootAppCache cache = new LocalSpringBootAppCache();
|
||||
|
||||
@@ -47,7 +47,7 @@ public class LocalSpringBootApp extends AbstractSpringBootApp {
|
||||
}
|
||||
|
||||
public static Collection<SpringBootApp> getAllRunningSpringApps() throws Exception {
|
||||
return getAllRunningJavaApps().stream().filter(SpringBootApp::isSpringBootApp).collect(CollectorUtil.toImmutableList());
|
||||
return getAllRunningJavaApps().stream().filter(SpringBootApp::isSpringApp).collect(CollectorUtil.toImmutableList());
|
||||
}
|
||||
|
||||
public LocalSpringBootApp(VirtualMachineDescriptor vmd) throws AttachNotSupportedException, IOException {
|
||||
@@ -92,9 +92,32 @@ public class LocalSpringBootApp extends AbstractSpringBootApp {
|
||||
return firstSpace < 0 ? rawName : rawName.substring(0, firstSpace);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpringApp() {
|
||||
if (isSpringApp == null) {
|
||||
try {
|
||||
isSpringApp = !containsSystemProperty("sts4.languageserver.name")
|
||||
&& (
|
||||
isSpringAppClasspath() ||
|
||||
providesNonBootLiveBeans()
|
||||
);
|
||||
} catch (Exception e) {
|
||||
//Couldn't determine if the VM is a spring boot app. Could be it already died. Or could be its not accessible (yet).
|
||||
// We will ignore the exception, pretend its not a boot app (most likely isn't) but DO NOT CACHE this result
|
||||
// so it will be retried again on the next polling loop.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return isSpringApp;
|
||||
}
|
||||
|
||||
private boolean isSpringAppClasspath() throws Exception {
|
||||
return contains(getClasspath(), "spring-core");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpringBootApp() {
|
||||
if (isSpringBootApp==null) {
|
||||
if (isSpringBootApp == null) {
|
||||
try {
|
||||
isSpringBootApp = !containsSystemProperty("sts4.languageserver.name")
|
||||
&& (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2019 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
|
||||
@@ -64,11 +64,21 @@ public class RemoteSpringBootApp extends AbstractSpringBootApp {
|
||||
|
||||
@Override
|
||||
public boolean isSpringBootApp() {
|
||||
return isSpringApp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSpringApp() {
|
||||
//For now, let's assume that, if its not a boot app, then we won't create a RemoteSpringBootApp instance for it.
|
||||
//The check that is here really only determines whether there's a process reachable at the remote jmx url.
|
||||
return getProcessID() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean providesNonBootLiveBeans() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getProcessID() {
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2018 Pivotal, Inc.
|
||||
* Copyright (c) 2018, 2019 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
|
||||
@@ -29,11 +29,16 @@ public interface SpringBootApp extends Disposable {
|
||||
String getHost() throws Exception;
|
||||
String getPort() throws Exception;
|
||||
String getContextPath() throws Exception;
|
||||
|
||||
boolean isSpringBootApp();
|
||||
boolean isSpringApp();
|
||||
|
||||
String getEnvironment() throws Exception;
|
||||
Collection<RequestMapping> getRequestMappings() throws Exception;
|
||||
|
||||
LiveBeansModel getBeans();
|
||||
boolean providesNonBootLiveBeans();
|
||||
|
||||
List<String> getActiveProfiles();
|
||||
Optional<List<LiveConditional>> getLiveConditionals() throws Exception;
|
||||
Properties getSystemProperties() throws Exception;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* Copyright (c) 2017, 2019 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
|
||||
@@ -98,7 +98,7 @@ public class BootLanguageServerParams {
|
||||
indexProvider.setProgressService(server.getProgressService());
|
||||
|
||||
return new BootLanguageServerParams(
|
||||
jdtProjectCache.filter(SpringProjectUtil::isBootProject),
|
||||
jdtProjectCache.filter(project -> SpringProjectUtil.isBootProject(project) || SpringProjectUtil.isSpringProject(project)),
|
||||
jdtProjectCache,
|
||||
indexProvider,
|
||||
(IDocument doc) -> new TypeUtil(jdtProjectCache.find(new TextDocumentIdentifier(doc.getUri()))),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017, 2018 Pivotal, Inc.
|
||||
* Copyright (c) 2017, 2019 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
|
||||
@@ -101,7 +101,21 @@ public class BootJavaHoverProvider implements HoverHandler {
|
||||
|
||||
Optional<IJavaProject> project = getProject(document);
|
||||
if (!project.isPresent()) return new CodeLens[0];
|
||||
if (!hasActuatorDependency(project.get())) return new CodeLens[0];
|
||||
|
||||
if (!hasActuatorDependency(project.get())) {
|
||||
// double check the running apps in case there is a non-boot app running with live beans enabled
|
||||
boolean nonBootLiveBeansAround = false;
|
||||
for (SpringBootApp bootApp : runningBootApps) {
|
||||
if (bootApp.providesNonBootLiveBeans()) {
|
||||
nonBootLiveBeansAround = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nonBootLiveBeansAround) {
|
||||
return new CodeLens[0];
|
||||
}
|
||||
}
|
||||
|
||||
return server.getCompilationUnitCache().withCompilationUnit(project.get(), URI.create(document.getUri()), cu -> {
|
||||
Collection<CodeLens> result = new LinkedHashSet<>();
|
||||
|
||||
Reference in New Issue
Block a user