Refactoring: introduce abstract 'SpringBootApp'

In preparation for supporting both local and
remote SpringBootApp as sources for live
information via jmx connection.
This commit is contained in:
Kris De Volder
2018-07-10 11:29:23 -07:00
parent 1383fff41d
commit e1c0953f4f
12 changed files with 670 additions and 600 deletions

View File

@@ -0,0 +1,590 @@
/*******************************************************************************
* Copyright (c) 2017, 2018 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
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.commons.boot.app.cli;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.Boot1xRequestMapping;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMappingsParser20;
import org.springframework.ide.vscode.commons.util.CollectorUtil;
import org.springframework.ide.vscode.commons.util.StringUtil;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
/**
* @author Martin Lippert
*/
public class LocalSpringBootApp extends SpringBootApp {
private static final String SPRINGFRAMEWORK_BOOT_DOMAIN = "org.springframework.boot";
private Logger logger = LoggerFactory.getLogger(LocalSpringBootApp.class);
private VirtualMachine vm;
private VirtualMachineDescriptor vmd;
private static final String LOCAL_CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress";
private Boolean isSpringBootApp;
private String jmxMbeanActuatorDomain;
// NOTE: Gson-based serialisation replaces the old Jackson ObjectMapper. Not sure if this makes a difference in the long run, but to retain the same output that Jackson Object Mapper
// was generating during serialisatino, some configuration in Gson is required, as the default behaviour of Gson is different than Object Mapper.
// Namely: Object Mapper does not escape Html, whereas Gson does by default (for example
// '=' in Gson appears as '\u003d')
private Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.create();
private final Supplier<String> jmxConnect = Suppliers.memoize(() -> {
String address = null;
try {
address = vm.getAgentProperties().getProperty(LOCAL_CONNECTOR_ADDRESS);
} catch (Exception e) {
//ignore
}
if (address==null) {
try {
address = vm.startLocalManagementAgent();
} catch (IOException e) {
logger.error("Error starting local management agent", e);
}
}
logger.info("Java PID = "+getProcessID()+" jmx address = "+address);
return address;
});
private static LocalSpringBootAppCache cache = new LocalSpringBootAppCache();
public static Collection<SpringBootApp> getAllRunningJavaApps() throws Exception {
return cache.getAllRunningJavaApps();
}
/**
* @return Map that contains the boot apps, mapping the process ID -> boot app accessor object
*/
public static Collection<SpringBootApp> getAllRunningSpringApps() throws Exception {
return getAllRunningJavaApps().stream().filter(SpringBootApp::isSpringBootApp).collect(CollectorUtil.toImmutableList());
}
public LocalSpringBootApp(VirtualMachineDescriptor vmd) throws AttachNotSupportedException, IOException {
this.vmd = vmd;
this.vm = VirtualMachine.attach(vmd);
logger.info("SpringBootApp created: "+this);
}
@Override
public String getProcessID() {
return vmd.id();
}
@Override
public String getProcessName() {
return vmd.displayName();
}
@Override
public String getHost() throws Exception {
JMXServiceURL serviceUrl = new JMXServiceURL(jmxConnect.get());
return serviceUrl.getHost();
}
@Override
public boolean isSpringBootApp() {
if (isSpringBootApp==null) {
try {
isSpringBootApp = !containsSystemProperty("sts4.languageserver.name")
&& (
isSpringBootAppClasspath() ||
isSpringBootAppSysprops()
);
} 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 isSpringBootApp;
}
private boolean isSpringBootAppSysprops() throws IOException {
Properties sysprops = this.vm.getSystemProperties();
return "org.springframework.boot.loader".equals(sysprops.getProperty("java.protocol.handler.pkgs"));
}
private boolean isSpringBootAppClasspath() throws IOException {
return contains(getClasspath(), "spring-boot");
}
@Override
public String[] getClasspath() throws IOException {
Properties props = this.vm.getSystemProperties();
String classpath = (String) props.get("java.class.path");
String[] cpElements = splitClasspath(classpath);
return cpElements;
}
@Override
public String getJavaCommand() throws IOException {
Properties props = this.vm.getSystemProperties();
return (String) props.get("sun.java.command");
}
public boolean containsSystemProperty(Object key) throws IOException {
Properties props = this.vm.getSystemProperties();
return props.containsKey(key);
}
@Override
public String getPort() throws Exception {
JMXConnector jmxConnector = null;
try {
jmxConnector = getJmxConnector();
return getPort(jmxConnector);
}
finally {
if (jmxConnector != null) jmxConnector.close();
}
}
@Override
public String getEnvironment() throws Exception {
Object result = getActuatorDataFromAttribute(getObjectName("type=Endpoint,name=environmentEndpoint"), "Data");
if (result != null) {
String environment = gson.toJson(result);
return environment;
}
result = getActuatorDataFromOperation(getObjectName("type=Endpoint,name=Env"), "environment");
if (result != null) {
String environment = gson.toJson(result);
return environment;
}
return null;
}
private String getBeansFromActuator(String domain) throws Exception {
Object result = getActuatorDataFromAttribute(getObjectName(domain, "type=Endpoint,name=beansEndpoint"), "Data");
if (result != null) {
String beans = gson.toJson(result);
return beans;
}
result = getActuatorDataFromOperation(getObjectName(domain, "type=Endpoint,name=Beans"), "beans");
if (result != null) {
String beans = gson.toJson(result);
return beans;
}
return null;
}
@Override
public LiveBeansModel getBeans() {
try {
String domain = getDomainForActuator();
String json = getBeansFromActuator(domain);
return LiveBeansModel.parse(json);
} catch (Exception e) {
logger.error("Error parsing beans", e);
return LiveBeansModel.builder().build();
}
}
public static Collection<RequestMapping> parseRequestMappingsJson(String json, String bootVersion) {
JSONObject obj = new JSONObject(json);
if (bootVersion.equals("2.x")) {
return RequestMappingsParser20.parse(obj);
} else { //1.x
List<RequestMapping> result = new ArrayList<>();
Iterator<String> keys = obj.keys();
while (keys.hasNext()) {
String rawKey = keys.next();
JSONObject value = obj.getJSONObject(rawKey);
result.add(new Boot1xRequestMapping(rawKey, value));
}
return result;
}
}
@Override
public Collection<RequestMapping> getRequestMappings() throws Exception {
//Boot 1.x
Object result = getActuatorDataFromAttribute(getObjectName("type=Endpoint,name=requestMappingEndpoint"), "Data");
if (result != null) {
String mappings = gson.toJson(result);
return parseRequestMappingsJson(mappings, "1.x");
}
//Boot 2.x
result = getActuatorDataFromOperation(getObjectName("type=Endpoint,name=Mappings"), "mappings");
if (result != null) {
String mappings = gson.toJson(result);
return parseRequestMappingsJson(mappings, "2.x");
}
return null;
}
@Override
public Optional<List<LiveConditional>> getLiveConditionals() throws Exception {
return getLiveConditionals(getAutoConfigReport(), getProcessID(), getProcessName());
}
/**
* Publicly visible so that it can be tested via a mock app
*
* @param autoConfigReport
* @param processId
* @param processName
* @return
* @throws Exception
*/
public static Optional<List<LiveConditional>> getLiveConditionals(String autoConfigReport, String processId,
String processName) {
return LiveConditionalParser.parse(autoConfigReport, processId, processName);
}
private String getAutoConfigReport() throws Exception {
//Boot 1.x
Object result = getActuatorDataFromAttribute(getObjectName("type=Endpoint,name=autoConfigurationReportEndpoint"), "Data");
if (result != null) {
String report = gson.toJson(result);
return report;
}
//Boot 2.x
result = getActuatorDataFromOperation(getObjectName("type=Endpoint,name=Conditions"), "applicationConditionEvaluation");
if (result != null) {
String report = gson.toJson(result);
return report;
}
return null;
}
protected Object getActuatorDataFromAttribute(ObjectName objectName, String attribute) throws Exception {
JMXConnector jmxConnector = null;
try {
if (objectName != null) {
jmxConnector = getJmxConnector();
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
try {
Object result = connection.getAttribute(objectName, "Data");
return result;
}
catch (InstanceNotFoundException e) {
}
}
return null;
}
finally {
if (jmxConnector != null) jmxConnector.close();
}
}
protected Object getActuatorDataFromOperation(ObjectName objectName, String operation) throws Exception {
JMXConnector jmxConnector = null;
try {
if (objectName != null) {
jmxConnector = getJmxConnector();
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
try {
Object result = connection.invoke(objectName, operation, null, null);
return result;
}
catch (InstanceNotFoundException e) {
}
}
return null;
}
finally {
if (jmxConnector != null) jmxConnector.close();
}
}
protected JMXConnector getJmxConnector() throws MalformedURLException, IOException {
JMXServiceURL serviceUrl = new JMXServiceURL(jmxConnect.get());
JMXConnector jmxConnector = JMXConnectorFactory.connect(serviceUrl, null);
return jmxConnector;
}
protected boolean contains(String[] cpElements, String element) {
for (String cpElement : cpElements) {
if (cpElement.contains(element)) {
return true;
}
}
return false;
}
protected String[] splitClasspath(String classpath) {
List<String> classpathElements = new ArrayList<>();
if (classpath != null) {
StringTokenizer tokenizer = new StringTokenizer(classpath, File.pathSeparator);
while (tokenizer.hasMoreTokens()) {
String classpathElement = tokenizer.nextToken();
classpathElements.add(classpathElement);
}
}
return classpathElements.toArray(new String[classpathElements.size()]);
}
protected String getPort(JMXConnector jmxConnector) throws Exception {
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
String port = getPortViaAdmin(connection);
if (port != null) {
return port;
}
port = getPortViaActuator(connection);
if (port != null) {
return port;
}
port = getPortViaTomcatBean(connection);
return port;
}
protected String getPortViaAdmin(MBeanServerConnection connection) throws Exception {
try {
String DEFAULT_OBJECT_NAME = "org.springframework.boot:type=Admin,name=SpringApplication";
ObjectName objectName = new ObjectName(DEFAULT_OBJECT_NAME);
Object o = connection.invoke(objectName,"getProperty", new String[] {"local.server.port"}, new String[] {String.class.getName()});
return o.toString();
}
catch (InstanceNotFoundException e) {
return null;
}
}
protected String getPortViaActuator(MBeanServerConnection connection) throws Exception {
String environment = getEnvironment();
if (environment != null) {
JSONObject env = new JSONObject(environment);
if (env != null) {
JSONObject portsObject = env.optJSONObject("server.ports");
if (portsObject != null) {
String portValue = portsObject.optString("local.server.port");
if (portValue!=null) {
return portValue;
}
}
//Not found as direct property value... in Boot 2.0 we must look inside the 'propertySources'.
//Similar... but structure is more complex.
JSONArray propertySources = env.optJSONArray("propertySources");
if (propertySources!=null) {
for (Object _source : propertySources) {
if (_source instanceof JSONObject) {
JSONObject source = (JSONObject) _source;
String sourceName = source.optString("name");
if ("server.ports".equals(sourceName)) {
JSONObject props = source.optJSONObject("properties");
JSONObject valueObject = props.optJSONObject("local.server.port");
if (valueObject!=null) {
String portValue = valueObject.optString("value");
if (portValue!=null) {
return portValue;
}
}
}
}
}
}
}
}
return null;
}
protected String getPortViaTomcatBean(MBeanServerConnection connection) throws Exception {
try {
Set<ObjectName> queryNames = connection.queryNames(null, null);
for (ObjectName objectName : queryNames) {
if (objectName.toString().startsWith("Tomcat") && objectName.toString().contains("type=Connector")) {
Object result = connection.getAttribute(objectName, "localPort");
if (result != null) {
return result.toString();
}
}
}
}
catch (InstanceNotFoundException e) {
}
return null;
}
protected ObjectName getObjectName(String keyProperties) throws Exception {
String domain = getDomainForActuator();
return getObjectName(domain, keyProperties);
}
protected ObjectName getObjectName(String domain, String keyProperties) throws Exception {
if (StringUtil.hasText(domain) && StringUtil.hasText(keyProperties)) {
String fullName = domain + ":" + keyProperties;
return ObjectName.getInstance(fullName);
}
return null;
}
/**
* PT 156072399: Actuator information can be defined using a different JMX MBean domain.
* By default, Spring Boot exposes management endpoints as JMX MBeans under the 'org.springframework.boot' domain.
* Users can however define another domain in the app's application.properties, for example using this property:
* management.endpoints.jmx.domain=com.example.myapp
* <b/>
* Therefore we need to support other domains than just: 'org.springframework.boot'
* @return JMX MBean domain containing actuator information, or null if not resolved.
* @throws Exception when resolving domain from JMX
*/
protected String getDomainForActuator() throws Exception {
if (this.jmxMbeanActuatorDomain == null) {
JMXConnector jmxConnector = null;
try {
jmxConnector = getJmxConnector();
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
// To be more efficient in finding the domain containing actuator information,
// and avoid many JMX connections
// first check the default springframework boot domain:
String beansJson = getBeansFromActuator(SPRINGFRAMEWORK_BOOT_DOMAIN);
if (StringUtil.hasText(beansJson)) {
this.jmxMbeanActuatorDomain = SPRINGFRAMEWORK_BOOT_DOMAIN;
}
if (this.jmxMbeanActuatorDomain == null) {
String[] domains = connection.getDomains();
if (domains != null) {
for (String domain : domains) {
// we already checked default boot domain, no need to check it again
// Note that default spring boot domain may still appear even if another
// domain contains actuator Beans (for example, "Admin" will be under default spring framework domain)
if (!SPRINGFRAMEWORK_BOOT_DOMAIN.equals(domain)) {
beansJson = getBeansFromActuator(domain);
if (StringUtil.hasText(beansJson)) {
this.jmxMbeanActuatorDomain = domain;
break;
}
}
}
}
}
} finally {
if (jmxConnector != null) {
jmxConnector.close();
}
}
}
return this.jmxMbeanActuatorDomain;
}
@Override
public String toString() {
return "Process [id=" +getProcessID() + ", name=`"+getProcessName()+"`]";
}
/**
* For testing / investigation purposes. Dumps out as much information as possible
* that can be onbtained from the jvm, without accessing JMX.
*/
public void dumpJvmInfo() throws IOException {
System.out.println("--- vm infos ----");
System.out.println("id = "+vm.id());
System.out.println("displayName = "+vmd.displayName());
dump("agentProperties", vm.getAgentProperties());
dump("systemProps", vm.getSystemProperties());
System.out.println("-----------------");
}
private void dump(String name, Properties props) {
System.out.println(name + " = {");
for (Entry<Object, Object> prop : props.entrySet()) {
System.out.println(" "+prop.getKey()+" = "+prop.getValue());
}
System.out.println("}");
}
@Override
public List<String> getActiveProfiles() {
try {
String _env = getEnvironment();
if (_env != null) {
JSONObject env = new JSONObject(_env);
Object _profiles = env.opt("activeProfiles"); //Boot 2.0
if (_profiles==null) {
_profiles = env.opt("profiles"); //Boot 1.5
}
if (_profiles instanceof JSONArray) {
JSONArray profiles = (JSONArray) _profiles;
ImmutableList.Builder<String> list = ImmutableList.builder();
for (Object object : profiles) {
if (object instanceof String) {
list.add((String) object);
}
}
return list.build();
}
}
} catch (Exception e) {
logger.error("error resolving profiles from env", e);
}
return null;
}
public void dispose() {
if (vm!=null) {
logger.info("SpringBootApp disposed: "+this);
try {
vm.detach();
} catch (Exception e) {
}
vm = null;
}
if (vmd!=null) {
vmd = null;
}
}
}

View File

@@ -15,34 +15,35 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
public class SpringBootAppCache {
public class LocalSpringBootAppCache {
private static final Duration EXPIRE_AFTER = Duration.ofMillis(500); //Limits rate at which we refresh list of apps
private long nextRefreshAfter = Long.MIN_VALUE;
private ImmutableMap<VirtualMachineDescriptor, SpringBootApp> apps = ImmutableMap.of();
private ImmutableMap<VirtualMachineDescriptor, LocalSpringBootApp> apps = ImmutableMap.of();
public synchronized Collection<SpringBootApp> getAllRunningJavaApps() {
if (System.currentTimeMillis()>=nextRefreshAfter) {
refresh();
}
return apps.values();
return ImmutableList.copyOf(apps.values());
}
private void refresh() {
List<VirtualMachineDescriptor> currentVms = VirtualMachine.list();
ImmutableMap.Builder<VirtualMachineDescriptor, SpringBootApp> newAppsBuilder = ImmutableMap.builder();
ImmutableMap.Builder<VirtualMachineDescriptor, LocalSpringBootApp> newAppsBuilder = ImmutableMap.builder();
for (VirtualMachineDescriptor vm : currentVms) {
SpringBootApp existingApp = apps.get(vm);
LocalSpringBootApp existingApp = apps.get(vm);
if (existingApp!=null) {
newAppsBuilder.put(vm, existingApp);
} else {
try {
newAppsBuilder.put(vm, new SpringBootApp(vm));
newAppsBuilder.put(vm, new LocalSpringBootApp(vm));
} catch (Exception e) {
//Ignore problems attaching to a VM. We will try again on next polling loop, if vm still exists.
//The most likely cause is that the VM already died since we obtained a reference to it.
@@ -50,7 +51,7 @@ public class SpringBootAppCache {
}
}
HashSet<VirtualMachineDescriptor> oldVms = new HashSet<>(apps.keySet());
ImmutableMap<VirtualMachineDescriptor, SpringBootApp> newApps = newAppsBuilder.build();
ImmutableMap<VirtualMachineDescriptor, LocalSpringBootApp> newApps = newAppsBuilder.build();
oldVms.removeAll(newApps.keySet());
for (VirtualMachineDescriptor oldVm : oldVms) {
apps.get(oldVm).dispose();

View File

@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2017, 2018 Pivotal, Inc.
* Copyright (c) 2018 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,570 +10,33 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.boot.app.cli;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import org.json.JSONArray;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.Boot1xRequestMapping;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMappingsParser20;
import org.springframework.ide.vscode.commons.util.CollectorUtil;
import org.springframework.ide.vscode.commons.util.StringUtil;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
/**
* @author Martin Lippert
* A abstract base class which attempts to capture commonalities between
* Local and Remote connections to SpringBootApp using JMX.
*/
public class SpringBootApp {
public abstract class SpringBootApp {
private static final String SPRINGFRAMEWORK_BOOT_DOMAIN = "org.springframework.boot";
public abstract String getProcessID();
public abstract String getProcessName();
public abstract boolean isSpringBootApp();
public abstract String[] getClasspath() throws IOException;
public abstract String getJavaCommand() throws IOException;
public abstract String getHost() throws Exception;
public abstract String getPort() throws Exception;
private Logger logger = LoggerFactory.getLogger(SpringBootApp.class);
private VirtualMachine vm;
private VirtualMachineDescriptor vmd;
private static final String LOCAL_CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress";
private Boolean isSpringBootApp;
private String jmxMbeanActuatorDomain;
// NOTE: Gson-based serialisation replaces the old Jackson ObjectMapper. Not sure if this makes a difference in the long run, but to retain the same output that Jackson Object Mapper
// was generating during serialisatino, some configuration in Gson is required, as the default behaviour of Gson is different than Object Mapper.
// Namely: Object Mapper does not escape Html, whereas Gson does by default (for example
// '=' in Gson appears as '\u003d')
private Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.create();
private final Supplier<String> jmxConnect = Suppliers.memoize(() -> {
String address = null;
try {
address = vm.getAgentProperties().getProperty(LOCAL_CONNECTOR_ADDRESS);
} catch (Exception e) {
//ignore
}
if (address==null) {
try {
address = vm.startLocalManagementAgent();
} catch (IOException e) {
logger.error("Error starting local management agent", e);
}
}
return address;
});
private static SpringBootAppCache cache = new SpringBootAppCache();
public static Collection<SpringBootApp> getAllRunningJavaApps() throws Exception {
return cache.getAllRunningJavaApps();
}
/**
* @return Map that contains the boot apps, mapping the process ID -> boot app accessor object
*/
public static Collection<SpringBootApp> getAllRunningSpringApps() throws Exception {
return getAllRunningJavaApps().stream().filter(SpringBootApp::isSpringBootApp).collect(CollectorUtil.toImmutableList());
}
public SpringBootApp(VirtualMachineDescriptor vmd) throws AttachNotSupportedException, IOException {
this.vmd = vmd;
this.vm = VirtualMachine.attach(vmd);
logger.info("SpringBootApp created: "+this);
}
public String getProcessID() {
return vmd.id();
}
public String getProcessName() {
return vmd.displayName();
}
public String getHost() throws Exception {
JMXServiceURL serviceUrl = new JMXServiceURL(jmxConnect.get());
return serviceUrl.getHost();
}
public boolean isSpringBootApp() {
if (isSpringBootApp==null) {
try {
isSpringBootApp = !containsSystemProperty("sts4.languageserver.name")
&& (
isSpringBootAppClasspath() ||
isSpringBootAppSysprops()
);
} 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 isSpringBootApp;
}
private boolean isSpringBootAppSysprops() throws IOException {
Properties sysprops = this.vm.getSystemProperties();
return "org.springframework.boot.loader".equals(sysprops.getProperty("java.protocol.handler.pkgs"));
}
private boolean isSpringBootAppClasspath() throws IOException {
return contains(getClasspath(), "spring-boot");
}
public String[] getClasspath() throws IOException {
Properties props = this.vm.getSystemProperties();
String classpath = (String) props.get("java.class.path");
String[] cpElements = splitClasspath(classpath);
return cpElements;
}
public String getJavaCommand() throws IOException {
Properties props = this.vm.getSystemProperties();
return (String) props.get("sun.java.command");
}
public boolean containsSystemProperty(Object key) throws IOException {
Properties props = this.vm.getSystemProperties();
return props.containsKey(key);
}
public String getPort() throws Exception {
JMXConnector jmxConnector = null;
try {
jmxConnector = getJmxConnector();
return getPort(jmxConnector);
}
finally {
if (jmxConnector != null) jmxConnector.close();
}
}
public String getEnvironment() throws Exception {
Object result = getActuatorDataFromAttribute(getObjectName("type=Endpoint,name=environmentEndpoint"), "Data");
if (result != null) {
String environment = gson.toJson(result);
return environment;
}
result = getActuatorDataFromOperation(getObjectName("type=Endpoint,name=Env"), "environment");
if (result != null) {
String environment = gson.toJson(result);
return environment;
}
return null;
}
private String getBeansFromActuator(String domain) throws Exception {
Object result = getActuatorDataFromAttribute(getObjectName(domain, "type=Endpoint,name=beansEndpoint"), "Data");
if (result != null) {
String beans = gson.toJson(result);
return beans;
}
result = getActuatorDataFromOperation(getObjectName(domain, "type=Endpoint,name=Beans"), "beans");
if (result != null) {
String beans = gson.toJson(result);
return beans;
}
return null;
}
public LiveBeansModel getBeans() {
try {
String domain = getDomainForActuator();
String json = getBeansFromActuator(domain);
return LiveBeansModel.parse(json);
} catch (Exception e) {
logger.error("Error parsing beans", e);
return LiveBeansModel.builder().build();
}
}
public static Collection<RequestMapping> parseRequestMappingsJson(String json, String bootVersion) {
JSONObject obj = new JSONObject(json);
if (bootVersion.equals("2.x")) {
return RequestMappingsParser20.parse(obj);
} else { //1.x
List<RequestMapping> result = new ArrayList<>();
Iterator<String> keys = obj.keys();
while (keys.hasNext()) {
String rawKey = keys.next();
JSONObject value = obj.getJSONObject(rawKey);
result.add(new Boot1xRequestMapping(rawKey, value));
}
return result;
}
}
public Collection<RequestMapping> getRequestMappings() throws Exception {
//Boot 1.x
Object result = getActuatorDataFromAttribute(getObjectName("type=Endpoint,name=requestMappingEndpoint"), "Data");
if (result != null) {
String mappings = gson.toJson(result);
return parseRequestMappingsJson(mappings, "1.x");
}
//Boot 2.x
result = getActuatorDataFromOperation(getObjectName("type=Endpoint,name=Mappings"), "mappings");
if (result != null) {
String mappings = gson.toJson(result);
return parseRequestMappingsJson(mappings, "2.x");
}
return null;
}
public Optional<List<LiveConditional>> getLiveConditionals() throws Exception {
return getLiveConditionals(getAutoConfigReport(), getProcessID(), getProcessName());
}
/**
* Publicly visible so that it can be tested via a mock app
*
* @param autoConfigReport
* @param processId
* @param processName
* @return
* @throws Exception
*/
public static Optional<List<LiveConditional>> getLiveConditionals(String autoConfigReport, String processId,
String processName) {
return LiveConditionalParser.parse(autoConfigReport, processId, processName);
}
private String getAutoConfigReport() throws Exception {
//Boot 1.x
Object result = getActuatorDataFromAttribute(getObjectName("type=Endpoint,name=autoConfigurationReportEndpoint"), "Data");
if (result != null) {
String report = gson.toJson(result);
return report;
}
//Boot 2.x
result = getActuatorDataFromOperation(getObjectName("type=Endpoint,name=Conditions"), "applicationConditionEvaluation");
if (result != null) {
String report = gson.toJson(result);
return report;
}
return null;
}
protected Object getActuatorDataFromAttribute(ObjectName objectName, String attribute) throws Exception {
JMXConnector jmxConnector = null;
try {
if (objectName != null) {
jmxConnector = getJmxConnector();
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
try {
Object result = connection.getAttribute(objectName, "Data");
return result;
}
catch (InstanceNotFoundException e) {
}
}
return null;
}
finally {
if (jmxConnector != null) jmxConnector.close();
}
}
protected Object getActuatorDataFromOperation(ObjectName objectName, String operation) throws Exception {
JMXConnector jmxConnector = null;
try {
if (objectName != null) {
jmxConnector = getJmxConnector();
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
try {
Object result = connection.invoke(objectName, operation, null, null);
return result;
}
catch (InstanceNotFoundException e) {
}
}
return null;
}
finally {
if (jmxConnector != null) jmxConnector.close();
}
}
protected JMXConnector getJmxConnector() throws MalformedURLException, IOException {
JMXServiceURL serviceUrl = new JMXServiceURL(jmxConnect.get());
JMXConnector jmxConnector = JMXConnectorFactory.connect(serviceUrl, null);
return jmxConnector;
}
protected boolean contains(String[] cpElements, String element) {
for (String cpElement : cpElements) {
if (cpElement.contains(element)) {
return true;
}
}
return false;
}
protected String[] splitClasspath(String classpath) {
List<String> classpathElements = new ArrayList<>();
if (classpath != null) {
StringTokenizer tokenizer = new StringTokenizer(classpath, File.pathSeparator);
while (tokenizer.hasMoreTokens()) {
String classpathElement = tokenizer.nextToken();
classpathElements.add(classpathElement);
}
}
return classpathElements.toArray(new String[classpathElements.size()]);
}
protected String getPort(JMXConnector jmxConnector) throws Exception {
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
String port = getPortViaAdmin(connection);
if (port != null) {
return port;
}
port = getPortViaActuator(connection);
if (port != null) {
return port;
}
port = getPortViaTomcatBean(connection);
return port;
}
protected String getPortViaAdmin(MBeanServerConnection connection) throws Exception {
try {
String DEFAULT_OBJECT_NAME = "org.springframework.boot:type=Admin,name=SpringApplication";
ObjectName objectName = new ObjectName(DEFAULT_OBJECT_NAME);
Object o = connection.invoke(objectName,"getProperty", new String[] {"local.server.port"}, new String[] {String.class.getName()});
return o.toString();
}
catch (InstanceNotFoundException e) {
return null;
}
}
protected String getPortViaActuator(MBeanServerConnection connection) throws Exception {
String environment = getEnvironment();
if (environment != null) {
JSONObject env = new JSONObject(environment);
if (env != null) {
JSONObject portsObject = env.optJSONObject("server.ports");
if (portsObject != null) {
String portValue = portsObject.optString("local.server.port");
if (portValue!=null) {
return portValue;
}
}
//Not found as direct property value... in Boot 2.0 we must look inside the 'propertySources'.
//Similar... but structure is more complex.
JSONArray propertySources = env.optJSONArray("propertySources");
if (propertySources!=null) {
for (Object _source : propertySources) {
if (_source instanceof JSONObject) {
JSONObject source = (JSONObject) _source;
String sourceName = source.optString("name");
if ("server.ports".equals(sourceName)) {
JSONObject props = source.optJSONObject("properties");
JSONObject valueObject = props.optJSONObject("local.server.port");
if (valueObject!=null) {
String portValue = valueObject.optString("value");
if (portValue!=null) {
return portValue;
}
}
}
}
}
}
}
}
return null;
}
protected String getPortViaTomcatBean(MBeanServerConnection connection) throws Exception {
try {
Set<ObjectName> queryNames = connection.queryNames(null, null);
for (ObjectName objectName : queryNames) {
if (objectName.toString().startsWith("Tomcat") && objectName.toString().contains("type=Connector")) {
Object result = connection.getAttribute(objectName, "localPort");
if (result != null) {
return result.toString();
}
}
}
}
catch (InstanceNotFoundException e) {
}
return null;
}
protected ObjectName getObjectName(String keyProperties) throws Exception {
String domain = getDomainForActuator();
return getObjectName(domain, keyProperties);
}
protected ObjectName getObjectName(String domain, String keyProperties) throws Exception {
if (StringUtil.hasText(domain) && StringUtil.hasText(keyProperties)) {
String fullName = domain + ":" + keyProperties;
return ObjectName.getInstance(fullName);
}
return null;
}
/**
* PT 156072399: Actuator information can be defined using a different JMX MBean domain.
* By default, Spring Boot exposes management endpoints as JMX MBeans under the 'org.springframework.boot' domain.
* Users can however define another domain in the app's application.properties, for example using this property:
* management.endpoints.jmx.domain=com.example.myapp
* <b/>
* Therefore we need to support other domains than just: 'org.springframework.boot'
* @return JMX MBean domain containing actuator information, or null if not resolved.
* @throws Exception when resolving domain from JMX
*/
protected String getDomainForActuator() throws Exception {
if (this.jmxMbeanActuatorDomain == null) {
JMXConnector jmxConnector = null;
try {
jmxConnector = getJmxConnector();
MBeanServerConnection connection = jmxConnector.getMBeanServerConnection();
// To be more efficient in finding the domain containing actuator information,
// and avoid many JMX connections
// first check the default springframework boot domain:
String beansJson = getBeansFromActuator(SPRINGFRAMEWORK_BOOT_DOMAIN);
if (StringUtil.hasText(beansJson)) {
this.jmxMbeanActuatorDomain = SPRINGFRAMEWORK_BOOT_DOMAIN;
}
if (this.jmxMbeanActuatorDomain == null) {
String[] domains = connection.getDomains();
if (domains != null) {
for (String domain : domains) {
// we already checked default boot domain, no need to check it again
// Note that default spring boot domain may still appear even if another
// domain contains actuator Beans (for example, "Admin" will be under default spring framework domain)
if (!SPRINGFRAMEWORK_BOOT_DOMAIN.equals(domain)) {
beansJson = getBeansFromActuator(domain);
if (StringUtil.hasText(beansJson)) {
this.jmxMbeanActuatorDomain = domain;
break;
}
}
}
}
}
} finally {
if (jmxConnector != null) {
jmxConnector.close();
}
}
}
return this.jmxMbeanActuatorDomain;
}
@Override
public String toString() {
return "Process [id=" +getProcessID() + ", name=`"+getProcessName()+"`]";
}
/**
* For testing / investigation purposes. Dumps out as much information as possible
* that can be onbtained from the jvm, without accessing JMX.
*/
public void dumpJvmInfo() throws IOException {
System.out.println("--- vm infos ----");
System.out.println("id = "+vm.id());
System.out.println("displayName = "+vmd.displayName());
dump("agentProperties", vm.getAgentProperties());
dump("systemProps", vm.getSystemProperties());
System.out.println("-----------------");
}
private void dump(String name, Properties props) {
System.out.println(name + " = {");
for (Entry<Object, Object> prop : props.entrySet()) {
System.out.println(" "+prop.getKey()+" = "+prop.getValue());
}
System.out.println("}");
}
public List<String> getActiveProfiles() {
try {
String _env = getEnvironment();
if (_env != null) {
JSONObject env = new JSONObject(_env);
Object _profiles = env.opt("activeProfiles"); //Boot 2.0
if (_profiles==null) {
_profiles = env.opt("profiles"); //Boot 1.5
}
if (_profiles instanceof JSONArray) {
JSONArray profiles = (JSONArray) _profiles;
ImmutableList.Builder<String> list = ImmutableList.builder();
for (Object object : profiles) {
if (object instanceof String) {
list.add((String) object);
}
}
return list.build();
}
}
} catch (Exception e) {
logger.error("error resolving profiles from env", e);
}
return null;
}
public void dispose() {
if (vm!=null) {
logger.info("SpringBootApp disposed: "+this);
try {
vm.detach();
} catch (Exception e) {
}
vm = null;
}
if (vmd!=null) {
vmd = null;
}
}
public abstract Optional<List<LiveConditional>> getLiveConditionals() throws Exception;
public abstract Collection<RequestMapping> getRequestMappings() throws Exception;
public abstract LiveBeansModel getBeans();
public abstract List<String> getActiveProfiles();
public abstract String getEnvironment() throws Exception;
}

View File

@@ -18,7 +18,7 @@ import java.util.Collection;
public class SpringBootAppCLI {
public static void main(String[] args) throws Exception {
Collection<SpringBootApp> allRunningJavaApps = SpringBootApp.getAllRunningJavaApps();
Collection<SpringBootApp> allRunningJavaApps = LocalSpringBootApp.getAllRunningJavaApps();
for (SpringBootApp app : allRunningJavaApps) {
if (app.isSpringBootApp()) {
printBootAppDetails(app);

View File

@@ -22,6 +22,7 @@ import java.util.Collection;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.json.JSONObject;
@@ -39,7 +40,7 @@ import org.springframework.ide.vscode.commons.util.test.ACondition;
import com.google.common.collect.ImmutableList;
public class SpringBootAppTest {
public class LocalSpringBootAppTest {
private static final String[] appNames = {
"actuator-client-15-test-subject", // Boot 1.5 test app
@@ -57,7 +58,7 @@ public class SpringBootAppTest {
public static void setupClass() throws Exception {
testAppRunners = Arrays.asList(appNames).stream().map(appName -> {
try {
return startTestApplication(SpringBootAppTest.class.getResource("/boot-apps/"+appName+"-0.0.1-SNAPSHOT.jar"));
return startTestApplication(LocalSpringBootAppTest.class.getResource("/boot-apps/"+appName+"-0.0.1-SNAPSHOT.jar"));
} catch (Exception e) {
throw ExceptionUtil.unchecked(e);
}
@@ -88,11 +89,13 @@ public class SpringBootAppTest {
);
}
private SpringBootApp getAppContaining(String nameFragment) {
private LocalSpringBootApp getAppContaining(String nameFragment) {
try {
Collection<SpringBootApp> allApps = SpringBootApp.getAllRunningJavaApps();
Collection<SpringBootApp> allApps = LocalSpringBootApp.getAllRunningJavaApps();
try {
return allApps.stream().filter(app -> app.getProcessName().contains(nameFragment)).findAny().get();
SpringBootApp result = allApps.stream().filter(localAppWithNameContaining(nameFragment))
.findAny().get();
return (LocalSpringBootApp) result;
} catch (NoSuchElementException e) {
//Improve error message for debugging...
StringBuilder foundAppNames = new StringBuilder();
@@ -107,35 +110,44 @@ public class SpringBootAppTest {
}
}
private Predicate<SpringBootApp> localAppWithNameContaining(String nameFragment) {
return app -> {
if (app instanceof LocalSpringBootApp) {
return ((LocalSpringBootApp)app).getProcessName().contains(nameFragment);
}
return false;
};
}
@Ignore @Test public void dumpJvmInfo() throws Exception {
//Ignored because this test may have timing issues. Still useful to
// run locally and inspect dump results, but may need some tweaking.
for (String appName : appNames) {
SpringBootApp testApp = getAppContaining(appName);
LocalSpringBootApp testApp = getAppContaining(appName);
testApp.dumpJvmInfo();
System.out.println("======================================");
}
}
@Test public void getAllJavaApps() throws Exception {
Collection<SpringBootApp> allApps = SpringBootApp.getAllRunningJavaApps();
Collection<SpringBootApp> allApps = LocalSpringBootApp.getAllRunningJavaApps();
for (String appName : appNames) {
Optional<SpringBootApp> myProcess = allApps.stream().filter(app -> app.getProcessName().contains(appName)).findAny();
Optional<SpringBootApp> myProcess = allApps.stream().filter(localAppWithNameContaining(appName)).findAny();
assertTrue(appName, myProcess.isPresent());
}
}
@Test public void getAllBootApps() throws Exception {
Collection<SpringBootApp> allApps = SpringBootApp.getAllRunningSpringApps();
Collection<SpringBootApp> allApps = LocalSpringBootApp.getAllRunningSpringApps();
for (String appName : appNames) {
Optional<SpringBootApp> myProcess = allApps.stream().filter(app -> app.getProcessName().contains(appName)).findAny();
Optional<SpringBootApp> myProcess = allApps.stream().filter(localAppWithNameContaining(appName)).findAny();
assertTrue(myProcess.isPresent());
}
}
@Test
public void getPort() throws Exception {
for (SpringBootApp testApp : getTestApps()) {
for (LocalSpringBootApp testApp : getTestApps()) {
ACondition.waitFor(TIMEOUT, () -> {
int port = Integer.parseInt(testApp.getPort());
assertTrue(port > 0);
@@ -144,7 +156,7 @@ public class SpringBootAppTest {
}
}
private Collection<SpringBootApp> getTestApps() throws Exception {
private Collection<LocalSpringBootApp> getTestApps() throws Exception {
return ACondition.waitForValue(TIMEOUT, () -> Arrays.asList(appNames).stream()
.map(this::getAppContaining)
.collect(Collectors.toList())
@@ -153,7 +165,7 @@ public class SpringBootAppTest {
@Test
public void getHost() throws Exception {
for (SpringBootApp testApp : getTestApps()) {
for (LocalSpringBootApp testApp : getTestApps()) {
System.err.println("getHost for "+testApp);
ACondition.waitFor(TIMEOUT, () -> {
String host = testApp.getHost();
@@ -165,7 +177,7 @@ public class SpringBootAppTest {
@Test
public void getEnvironment() throws Exception {
for (SpringBootApp testApp : getTestApps()) {
for (LocalSpringBootApp testApp : getTestApps()) {
ACondition.waitFor(TIMEOUT, () -> {
String env = testApp.getEnvironment();
assertNonEmptyJsonObject(env);
@@ -175,7 +187,7 @@ public class SpringBootAppTest {
@Test
public void getBeans() throws Exception {
for (SpringBootApp testApp : getTestApps()) {
for (LocalSpringBootApp testApp : getTestApps()) {
try {
ACondition.waitFor(TIMEOUT, () -> {
LiveBeansModel beansModel = testApp.getBeans();
@@ -191,7 +203,7 @@ public class SpringBootAppTest {
@Test
public void getRequestMappings() throws Exception {
for (SpringBootApp testApp : getTestApps()) {
for (LocalSpringBootApp testApp : getTestApps()) {
try {
ACondition.waitFor(TIMEOUT, () -> {
Collection<RequestMapping> result = testApp.getRequestMappings();
@@ -206,7 +218,7 @@ public class SpringBootAppTest {
@Test
public void getLiveConditionals() throws Exception {
for (SpringBootApp testApp : getTestApps()) {
for (LocalSpringBootApp testApp : getTestApps()) {
try {
ACondition.waitFor(TIMEOUT, () -> {
Optional<List<LiveConditional>> result = testApp.getLiveConditionals();
@@ -221,7 +233,7 @@ public class SpringBootAppTest {
@Test
public void getProfiles() throws Exception {
for (SpringBootApp testApp : getTestApps()) {
for (LocalSpringBootApp testApp : getTestApps()) {
ACondition.waitFor(TIMEOUT, () -> {
List<String> result = testApp.getActiveProfiles();
assertEquals(ImmutableList.copyOf(TEST_PROFILES), result);

View File

@@ -28,6 +28,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
import org.springframework.ide.vscode.boot.java.livehover.LiveHoverUtils;
import org.springframework.ide.vscode.commons.boot.app.cli.LiveConditional;
import org.springframework.ide.vscode.commons.boot.app.cli.LocalSpringBootApp;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.util.Log;

View File

@@ -12,13 +12,16 @@ package org.springframework.ide.vscode.boot.java.handlers;
import java.util.Collection;
import org.springframework.ide.vscode.commons.boot.app.cli.LocalSpringBootApp;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import com.google.common.collect.ImmutableList;
public interface RunningAppProvider {
public static final RunningAppProvider DEFAULT = SpringBootApp::getAllRunningSpringApps;
public static final RunningAppProvider LOCAL_APPS = LocalSpringBootApp::getAllRunningSpringApps;
public static final RunningAppProvider DEFAULT = LocalSpringBootApp::getAllRunningSpringApps;
public static final RunningAppProvider NULL = () -> ImmutableList.of();
Collection<SpringBootApp> getAllRunningSpringApps() throws Exception;

View File

@@ -36,9 +36,9 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.collect.ImmutableList;
public abstract class AbstractInjectedIntoHoverProvider implements HoverProvider {
protected BootJavaLanguageServerComponents server;
public AbstractInjectedIntoHoverProvider(BootJavaLanguageServerComponents server) {
this.server = server;
}

View File

@@ -57,13 +57,13 @@ public class ActiveProfilesProvider implements HoverProvider {
for (SpringBootApp app : runningApps) {
List<String> profiles = app.getActiveProfiles();
if (profiles==null) {
markdown.append(niceAppName(app)+" : _Unknown_\n\n");
markdown.append(LiveHoverUtils.niceAppName(app)+" : _Unknown_\n\n");
} else {
hasInterestingApp = true;
if (profiles.isEmpty()) {
markdown.append(niceAppName(app)+" : _None_\n\n");
markdown.append(LiveHoverUtils.niceAppName(app)+" : _None_\n\n");
} else {
markdown.append(niceAppName(app)+" :\n");
markdown.append(LiveHoverUtils.niceAppName(app)+" :\n");
for (String profile : profiles) {
markdown.append("- "+profile+"\n");
}
@@ -80,10 +80,6 @@ public class ActiveProfilesProvider implements HoverProvider {
return null;
}
private String niceAppName(SpringBootApp app) {
return "Process [PID="+app.getProcessID()+", name=`"+app.getProcessName()+"`]";
}
@Override
public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
if (runningApps.length > 0) {

View File

@@ -26,7 +26,7 @@ import org.springframework.ide.vscode.commons.util.Renderables;
import org.springframework.ide.vscode.commons.util.StringUtil;
public class LiveHoverUtils {
public static String showBean(LiveBean bean) {
StringBuilder buf = new StringBuilder("Bean [id: " + bean.getId());
String type = bean.getType(true);
@@ -69,14 +69,6 @@ public class LiveHoverUtils {
return new SpringResource(sourceLinks, resource, project).toMarkdown();
}
public static String niceAppName(SpringBootApp app) {
return niceAppName(app.getProcessID() ,app.getProcessName());
}
public static String niceAppName(String processId, String processName) {
return "Process [PID=" + processId + ", name=`" + processName + "`]";
}
public static boolean hasRelevantBeans(SpringBootApp app, LiveBean definedBean) {
return findRelevantBeans(app, definedBean).findAny().isPresent();
}
@@ -94,4 +86,13 @@ public class LiveHoverUtils {
return Stream.empty();
}
public static String niceAppName(SpringBootApp app) {
return niceAppName(app.getProcessID(), app.getProcessName());
}
public static String niceAppName(String processId, String processName) {
return "Process [PID="+processId+", name=`"+processName+"`]";
}
}

View File

@@ -12,6 +12,7 @@ package org.springframework.ide.vscode.boot.java.requestmapping;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;
@@ -22,6 +23,7 @@ import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping;
import org.springframework.ide.vscode.commons.util.Log;
/**

View File

@@ -18,6 +18,7 @@ import java.util.Collection;
import org.mockito.Mockito;
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
import org.springframework.ide.vscode.commons.boot.app.cli.LocalSpringBootApp;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping;
@@ -106,13 +107,13 @@ public class MockRunningAppProvider {
}
public MockAppBuilder requestMappings(String mappings) throws Exception {
Collection<RequestMapping> requestMappings = SpringBootApp.parseRequestMappingsJson(mappings, "1.x");
Collection<RequestMapping> requestMappings = LocalSpringBootApp.parseRequestMappingsJson(mappings, "1.x");
when(app.getRequestMappings()).thenReturn(requestMappings);
return this;
}
public MockAppBuilder liveConditionalsJson(String rawJson) throws Exception{
when(app.getLiveConditionals()).thenReturn(SpringBootApp.getLiveConditionals(rawJson, processId, processName));
when(app.getLiveConditionals()).thenReturn(LocalSpringBootApp.getLiveConditionals(rawJson, processId, processName));
return this;
}