PT #151428541: first steps to gather live hover hint data from running apps

This commit is contained in:
Martin Lippert
2017-10-02 16:30:33 +02:00
parent 0d75bd4b56
commit 7c4c3f50f5
7 changed files with 348 additions and 63 deletions

View File

@@ -12,9 +12,11 @@ package org.springframework.ide.vscode.boot.java;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.InitializedParams;
import org.eclipse.lsp4j.InitializeParams;
import org.eclipse.lsp4j.InitializeResult;
import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider;
import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeLensEngine;
@@ -32,9 +34,10 @@ import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingHov
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingSymbolProvider;
import org.springframework.ide.vscode.boot.java.scope.ScopeCompletionProcessor;
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippet;
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetManager;
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetContext;
import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetManager;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexer;
import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog;
import org.springframework.ide.vscode.boot.java.value.ValueCompletionProcessor;
import org.springframework.ide.vscode.boot.java.value.ValueHoverProvider;
import org.springframework.ide.vscode.boot.java.value.ValuePropertyReferencesProvider;
@@ -47,7 +50,6 @@ import org.springframework.ide.vscode.commons.languageserver.java.DefaultJavaPro
import org.springframework.ide.vscode.commons.languageserver.java.IJavaProjectFinderStrategy;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine;
import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler;
import org.springframework.ide.vscode.commons.languageserver.util.ReferencesHandler;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
@@ -66,6 +68,8 @@ import com.google.common.collect.ImmutableList;
*/
public class BootJavaLanguageServer extends SimpleLanguageServer {
public static final String LANGUAGE_SERVER_PROCESS_PROPERTY = "spring-boot-language-server";
public static final JavaProjectFinder DEFAULT_PROJECT_FINDER = new DefaultJavaProjectFinder(new IJavaProjectFinderStrategy[] {
new MavenProjectFinderStrategy(MavenCore.getDefault()),
new GradleProjectFinderStrategy(GradleCore.getDefault()),
@@ -74,9 +78,13 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
private final VscodeCompletionEngineAdapter completionEngine;
private final SpringIndexer indexer;
private final SpringLiveHoverWatchdog liveHoverWatchdog;
public BootJavaLanguageServer(JavaProjectFinder javaProjectFinder, SpringPropertyIndexProvider indexProvider) {
super("vscode-boot-java");
System.setProperty(LANGUAGE_SERVER_PROCESS_PROPERTY, LANGUAGE_SERVER_PROCESS_PROPERTY);
SimpleWorkspaceService workspaceService = getWorkspaceService();
SimpleTextDocumentService documents = getTextDocumentService();
@@ -92,9 +100,16 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
documents.onCompletion(completionEngine::getCompletions);
documents.onCompletionResolve(completionEngine::resolveCompletion);
HoverHandler hoverInfoProvider = createHoverHandler(javaProjectFinder);
BootJavaHoverProvider hoverInfoProvider = createHoverHandler(javaProjectFinder);
documents.onHover(hoverInfoProvider);
liveHoverWatchdog = new SpringLiveHoverWatchdog(this, hoverInfoProvider);
documents.onDidChangeContent(params -> {
TextDocument doc = params.getDocument();
liveHoverWatchdog.watchDocument(doc.getUri());
liveHoverWatchdog.update(doc.getUri());
});
ReferencesHandler referencesHandler = createReferenceHandler(this, javaProjectFinder);
documents.onReferences(referencesHandler);
@@ -112,9 +127,28 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
}
@Override
public void initialized(InitializedParams params) {
super.initialized(params);
public CompletableFuture<InitializeResult> initialize(InitializeParams params) {
CompletableFuture<InitializeResult> result = super.initialize(params);
this.indexer.initialize(this.getWorkspaceRoot());
this.liveHoverWatchdog.start();
return result;
}
@Override
public void initialized() {
// TODO: due to a missing message from lsp4e this "initialized" is not called in the LSP4E case
// if this gets fixed, the code should move here (from "initialize" above)
// this.indexer.initialize(this.getWorkspaceRoot());
// this.liveHoverWatchdog.start();
}
@Override
public CompletableFuture<Object> shutdown() {
liveHoverWatchdog.shutdown();
return super.shutdown();
}
protected ICompletionEngine createCompletionEngine(JavaProjectFinder javaProjectFinder, SpringPropertyIndexProvider indexProvider) {
@@ -184,7 +218,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
return new BootJavaCompletionEngine(javaProjectFinder, providers, snippetManager);
}
protected HoverHandler createHoverHandler(JavaProjectFinder javaProjectFinder) {
protected BootJavaHoverProvider createHoverHandler(JavaProjectFinder javaProjectFinder) {
HashMap<String, HoverProvider> providers = new HashMap<>();
providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE, new ValueHoverProvider());
providers.put(org.springframework.ide.vscode.boot.java.requestmapping.Constants.SPRING_REQUEST_MAPPING, new RequestMappingHoverProvider());

View File

@@ -11,6 +11,8 @@
package org.springframework.ide.vscode.boot.java.handlers;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
@@ -19,18 +21,26 @@ import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
@@ -68,7 +78,89 @@ public class BootJavaHoverProvider implements HoverHandler {
return SimpleTextDocumentService.NO_HOVER;
}
public Range[] getLiveHoverHints(final TextDocument document, final SpringBootApp[] runningBootApps) {
List<Range> result = new ArrayList<>();
try {
IJavaProject project = getProject(document);
CompilationUnit cu = parse(document, project);
cu.accept(new ASTVisitor() {
@Override
public boolean visit(SingleMemberAnnotation node) {
try {
extractLiveHints(node, document, runningBootApps, result);
}
catch (Exception e) {
e.printStackTrace();
}
return super.visit(node);
}
@Override
public boolean visit(NormalAnnotation node) {
try {
extractLiveHints(node, document, runningBootApps, result);
}
catch (Exception e) {
e.printStackTrace();
}
return super.visit(node);
}
@Override
public boolean visit(MarkerAnnotation node) {
try {
extractLiveHints(node, document, runningBootApps, result);
}
catch (Exception e) {
e.printStackTrace();
}
return super.visit(node);
}
});
}
catch (Exception e) {
e.printStackTrace();
}
return result.toArray(new Range[result.size()]);
}
protected void extractLiveHints(Annotation annotation, TextDocument document, SpringBootApp[] runningApps, List<Range> result) {
ITypeBinding type = annotation.resolveTypeBinding();
if (type != null) {
String qualifiedName = type.getQualifiedName();
if (qualifiedName != null) {
HoverProvider provider = this.hoverProviders.get(qualifiedName);
if (provider != null) {
Range range = provider.getLiveHoverHint(annotation, document, runningApps);
if (range != null) {
result.add(range);
}
}
}
}
}
private CompletableFuture<Hover> provideHover(TextDocument document, int offset) throws Exception {
IJavaProject project = getProject(document);
CompilationUnit cu = parse(document, project);
ASTNode node = NodeFinder.perform(cu, offset, 0);
if (node != null) {
System.out.println("AST node found: " + node.getClass().getName());
return provideHoverForAnnotation(node, offset, document, project);
}
return null;
}
private CompilationUnit parse(TextDocument document, IJavaProject project)
throws Exception, BadLocationException {
ASTParser parser = ASTParser.newParser(AST.JLS8);
Map<String, String> options = JavaCore.getOptions();
JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options);
@@ -78,7 +170,7 @@ public class BootJavaHoverProvider implements HoverHandler {
parser.setBindingsRecovery(true);
parser.setResolveBindings(true);
String[] classpathEntries = getClasspathEntries(document);
String[] classpathEntries = getClasspathEntries(project);
String[] sourceEntries = new String[] {};
parser.setEnvironment(classpathEntries, sourceEntries, null, true);
@@ -88,17 +180,10 @@ public class BootJavaHoverProvider implements HoverHandler {
parser.setSource(document.get(0, document.getLength()).toCharArray());
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
ASTNode node = NodeFinder.perform(cu, offset, 0);
if (node != null) {
System.out.println("AST node found: " + node.getClass().getName());
return provideHoverForAnnotation(node, offset, document);
}
return null;
return cu;
}
private CompletableFuture<Hover> provideHoverForAnnotation(ASTNode node, int offset, TextDocument doc) {
private CompletableFuture<Hover> provideHoverForAnnotation(ASTNode node, int offset, TextDocument doc, IJavaProject project) {
Annotation annotation = null;
while (node != null && !(node instanceof Annotation)) {
@@ -113,7 +198,8 @@ public class BootJavaHoverProvider implements HoverHandler {
if (qualifiedName != null) {
HoverProvider provider = this.hoverProviders.get(qualifiedName);
if (provider != null) {
return provider.provideHover(node, annotation, type, offset, doc);
SpringBootApp[] runningApps = getRunningSpringApps(project);
return provider.provideHover(node, annotation, type, offset, doc, runningApps);
}
}
}
@@ -122,8 +208,11 @@ public class BootJavaHoverProvider implements HoverHandler {
return null;
}
private String[] getClasspathEntries(IDocument doc) throws Exception {
IJavaProject project = this.projectFinder.find(doc);
private IJavaProject getProject(IDocument doc) throws Exception {
return this.projectFinder.find(doc);
}
private String[] getClasspathEntries(IJavaProject project) throws Exception {
IClasspath classpath = project.getClasspath();
Stream<Path> classpathEntries = classpath.getClasspathEntries();
return classpathEntries
@@ -131,4 +220,15 @@ public class BootJavaHoverProvider implements HoverHandler {
.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new);
}
private SpringBootApp[] getRunningSpringApps(IJavaProject project) {
try {
return SpringBootApp.getAllRunningSpringApps().values().stream()
.filter((app) -> !app.containsSystemProperty(BootJavaLanguageServer.LANGUAGE_SERVER_PROCESS_PROPERTY))
.toArray(SpringBootApp[]::new);
} catch (Exception e) {
e.printStackTrace();
return new SpringBootApp[0];
}
}
}

View File

@@ -16,6 +16,8 @@ import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.Range;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
@@ -23,7 +25,7 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
*/
public interface HoverProvider {
CompletableFuture<Hover> provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset,
TextDocument doc);
CompletableFuture<Hover> provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset, TextDocument doc, SpringBootApp[] runningApps);
Range getLiveHoverHint(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps);
}

View File

@@ -13,7 +13,6 @@ package org.springframework.ide.vscode.boot.java.requestmapping;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jdt.core.dom.ASTNode;
@@ -31,6 +30,7 @@ import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.json.JSONObject;
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
@@ -40,14 +40,32 @@ public class RequestMappingHoverProvider implements HoverProvider {
@Override
public CompletableFuture<Hover> provideHover(ASTNode node, Annotation annotation,
ITypeBinding type, int offset, TextDocument doc) {
return provideHover(annotation, doc);
ITypeBinding type, int offset, TextDocument doc, SpringBootApp[] runningApps) {
return provideHover(annotation, doc, runningApps);
}
private CompletableFuture<Hover> provideHover(Annotation annotation, TextDocument doc) {
@Override
public Range getLiveHoverHint(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
try {
if (runningApps.length > 0) {
// TODO: this check is too simple, we need to do a lot more here
// -> check if the running app has a matching request mapping for this annotation
Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength());
return hoverRange;
}
}
catch (BadLocationException e) {
e.printStackTrace();
}
return null;
}
private CompletableFuture<Hover> provideHover(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
try {
JSONObject[] mappings = getRequestMappingsFromProcesses();
JSONObject[] mappings = getRequestMappingsFromProcesses(runningApps);
List<Either<String, MarkedString>> hoverContent = new ArrayList<>();
@@ -55,7 +73,6 @@ public class RequestMappingHoverProvider implements HoverProvider {
addHoverContent(mappings[i], hoverContent, annotation);
}
Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength());
Hover hover = new Hover();
@@ -109,21 +126,16 @@ public class RequestMappingHoverProvider implements HoverProvider {
return mappingPath != null ? key.contains(mappingPath) : false;
}
public JSONObject[] getRequestMappingsFromProcesses() {
public JSONObject[] getRequestMappingsFromProcesses(SpringBootApp[] runningApps) {
List<JSONObject> result = new ArrayList<>();
try {
Map<String, SpringBootApp> apps = SpringBootApp.getAllRunningJavaApps();
Iterator<SpringBootApp> appsIter = apps.values().iterator();
while (appsIter.hasNext()) {
SpringBootApp app = appsIter.next();
if (app.isSpringBootApp()) {
String mappings = app.getRequestMappings();
if (mappings != null) {
JSONObject requestMappings = new JSONObject(mappings);
if (requestMappings != null) {
result.add(requestMappings);
}
for (SpringBootApp app : runningApps) {
String mappings = app.getRequestMappings();
if (mappings != null) {
JSONObject requestMappings = new JSONObject(mappings);
if (requestMappings != null) {
result.add(requestMappings);
}
}
}

View File

@@ -0,0 +1,117 @@
/*******************************************************************************
* Copyright (c) 2017 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.boot.java.utils;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentSkipListSet;
import org.eclipse.lsp4j.Range;
import org.springframework.ide.vscode.boot.java.BootJavaLanguageServer;
import org.springframework.ide.vscode.boot.java.handlers.BootJavaHoverProvider;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
* @author Martin Lippert
*/
public class SpringLiveHoverWatchdog {
private static final int POLLING_INTERVAL_MILLISECONDS = 5000;
private final Set<String> watchedDocs;
private final SimpleLanguageServer server;
private final BootJavaHoverProvider hoverProvider;
private final Timer timer;
public SpringLiveHoverWatchdog(SimpleLanguageServer server, BootJavaHoverProvider hoverProvider) {
this.server = server;
this.hoverProvider = hoverProvider;
this.watchedDocs = new ConcurrentSkipListSet<>();
this.timer = new Timer();
}
public void start() {
TimerTask task = new TimerTask() {
@Override
public void run() {
update();
}
};
timer.scheduleAtFixedRate(task, 0, POLLING_INTERVAL_MILLISECONDS);
}
public void shutdown() {
timer.cancel();
}
public void watchDocument(String docURI) {
this.watchedDocs.add(docURI);
}
public void unwatchDocument(String docURI) {
this.watchedDocs.remove(docURI);
cleanupLiveHints(docURI);
if (watchedDocs.size() == 0) {
cleanupResources();
}
}
public void update(String docURI) {
try {
SpringBootApp[] runningBootApps = SpringBootApp.getAllRunningSpringApps().values().stream()
.filter((app) -> !app.containsSystemProperty(BootJavaLanguageServer.LANGUAGE_SERVER_PROCESS_PROPERTY))
.toArray(SpringBootApp[]::new);
TextDocument doc = this.server.getTextDocumentService().get(docURI);
if (doc != null) {
Range[] ranges = this.hoverProvider.getLiveHoverHints(doc, runningBootApps);
publishLiveHints(ranges);
}
} catch (Exception e) {
e.printStackTrace();
}
}
protected void update() {
if (this.watchedDocs.size() > 0) {
for (String docURI : watchedDocs) {
update(docURI);
}
}
}
private void publishLiveHints(Range[] ranges) {
for (Range range : ranges) {
int startLine = range.getStart().getLine();
int startChar = range.getStart().getCharacter();
int endLine = range.getEnd().getLine();
int endChar = range.getEnd().getCharacter();
System.out.println("live hover information at: " + startLine + ":" + startChar + " - " + endLine + ":" + endChar);
}
}
private void cleanupLiveHints(String docURI) {
// TODO: send client a message to cleanup live hover diagnostics data
}
private void cleanupResources() {
// TODO: close and cleanup open JMX connections and cached data
}
}

View File

@@ -13,7 +13,6 @@ package org.springframework.ide.vscode.boot.java.value;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.eclipse.jdt.core.dom.ASTNode;
@@ -35,27 +34,22 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
*/
public class ValueHoverProvider implements HoverProvider {
public static void register(Map<String, HoverProvider> hoverProviders) {
ValueHoverProvider provider = new ValueHoverProvider();
hoverProviders.put(Constants.SPRING_VALUE, provider);
}
@Override
public CompletableFuture<Hover> provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset,
TextDocument doc) {
TextDocument doc, SpringBootApp[] runningApps) {
try {
// case: @Value("prefix<*>")
if (node instanceof StringLiteral && node.getParent() instanceof Annotation) {
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
return provideHover(node.toString(), offset - node.getStartPosition(), node.getStartPosition(), doc);
return provideHover(node.toString(), offset - node.getStartPosition(), node.getStartPosition(), doc, runningApps);
}
}
// case: @Value(value="prefix<*>")
else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair
&& "value".equals(((MemberValuePair)node.getParent()).getName().toString())) {
if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) {
return provideHover(node.toString(), offset - node.getStartPosition(), node.getStartPosition(), doc);
return provideHover(node.toString(), offset - node.getStartPosition(), node.getStartPosition(), doc, runningApps);
}
}
}
@@ -66,7 +60,12 @@ public class ValueHoverProvider implements HoverProvider {
return null;
}
private CompletableFuture<Hover> provideHover(String value, int offset, int nodeStartOffset, TextDocument doc) {
@Override
public Range getLiveHoverHint(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
return null;
}
private CompletableFuture<Hover> provideHover(String value, int offset, int nodeStartOffset, TextDocument doc, SpringBootApp[] runningApps) {
try {
LocalRange range = getPropertyRange(value, offset);
@@ -74,7 +73,7 @@ public class ValueHoverProvider implements HoverProvider {
String propertyKey = value.substring(range.getStart(), range.getEnd());
if (propertyKey != null) {
JSONObject[] allProperties = getPropertiesFromProcesses();
JSONObject[] allProperties = getPropertiesFromProcesses(runningApps);
List<Either<String, MarkedString>> hoverContent = new ArrayList<>();
@@ -114,21 +113,16 @@ public class ValueHoverProvider implements HoverProvider {
return null;
}
public JSONObject[] getPropertiesFromProcesses() {
public JSONObject[] getPropertiesFromProcesses(SpringBootApp[] runningApps) {
List<JSONObject> result = new ArrayList<>();
try {
Map<String, SpringBootApp> apps = SpringBootApp.getAllRunningJavaApps();
Iterator<SpringBootApp> appsIter = apps.values().iterator();
while (appsIter.hasNext()) {
SpringBootApp app = appsIter.next();
if (app.isSpringBootApp()) {
String environment = app.getEnvironment();
if (environment != null) {
JSONObject env = new JSONObject(environment);
if (env != null) {
result.add(env);
}
for (SpringBootApp app : runningApps) {
String environment = app.getEnvironment();
if (environment != null) {
JSONObject env = new JSONObject(environment);
if (env != null) {
result.add(env);
}
}
}