initial steps to support live bean hovers for non-boot projects

This commit is contained in:
Martin Lippert
2019-01-08 11:27:55 +01:00
parent 19904ca093
commit f0379020bc
6 changed files with 117 additions and 14 deletions

View File

@@ -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;
}

View File

@@ -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")
&& (

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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()))),

View File

@@ -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<>();