From 3387a91cfb1a8b4968930f02324eca9ac32a8551 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Tue, 18 Apr 2023 17:47:25 +0200 Subject: [PATCH] service method to return beans as future waits for project-related indexing tasks to complete --- .../vscode/boot/app/SpringSymbolIndex.java | 45 +++++++-- .../test/SpringModelServiceTest.java | 91 +++++++++++++++++++ 2 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringModelServiceTest.java diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java index 6ea90cfb1..1ba30a1ff 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/SpringSymbolIndex.java @@ -98,10 +98,11 @@ public class SpringSymbolIndex implements InitializingBean, SpringModelService { private final List symbols = new ArrayList<>(); private final ConcurrentMap> symbolsByDoc = new ConcurrentHashMap<>(); - private final ConcurrentMap> symbolsByProject = new ConcurrentHashMap<>(); private final ExecutorService updateQueue = Executors.newSingleThreadExecutor(); + private final Map> latestScheduledTaskByProject = new ConcurrentHashMap>(); + private SpringIndexer[] indexers; private ListenerList listeners = new ListenerList(); @@ -343,6 +344,7 @@ public class SpringSymbolIndex implements InitializingBean, SpringModelService { cf.thenAccept( f -> { projectInitializedFuture(project).complete(null); }); + return cf; } @@ -365,7 +367,10 @@ public class SpringSymbolIndex implements InitializingBean, SpringModelService { } CompletableFuture future = CompletableFuture.allOf(futures); + future.thenAccept(v -> listeners.fire(v)); + + this.latestScheduledTaskByProject.put(project.getElementName(), future); return future; } } else { @@ -389,7 +394,10 @@ public class SpringSymbolIndex implements InitializingBean, SpringModelService { return CompletableFuture.completedFuture(null); } else { DeleteProject initializeItem = new DeleteProject(project, this.indexers); - return CompletableFuture.runAsync(initializeItem, this.updateQueue); + CompletableFuture future = CompletableFuture.runAsync(initializeItem, this.updateQueue); + this.latestScheduledTaskByProject.put(project.getElementName(), future); + + return future; } } catch (Throwable e) { log.error("", e); @@ -545,7 +553,10 @@ public class SpringSymbolIndex implements InitializingBean, SpringModelService { Optional maybeProject = projectFinder().find(new TextDocumentIdentifier(deletedDocURI)); if (maybeProject.isPresent()) { DeleteItems deleteItem = new DeleteItems(maybeProject.get(), new String[] {deletedDocURI}, this.indexers); - return CompletableFuture.runAsync(deleteItem, this.updateQueue); + CompletableFuture future = CompletableFuture.runAsync(deleteItem, this.updateQueue); + + this.latestScheduledTaskByProject.put(maybeProject.get().getElementName(), future); + return future; } } catch (Exception e) { @@ -569,7 +580,10 @@ public class SpringSymbolIndex implements InitializingBean, SpringModelService { List docURIs = projectMapping.get(project); DeleteItems deleteItems = new DeleteItems(project, (String[]) docURIs.toArray(new String[docURIs.size()]), this.indexers); - futures.add(CompletableFuture.runAsync(deleteItems, this.updateQueue)); + CompletableFuture future = CompletableFuture.runAsync(deleteItems, this.updateQueue); + + this.latestScheduledTaskByProject.put(project.getElementName(), future); + futures.add(future); } CompletableFuture future = CompletableFuture.allOf((CompletableFuture[]) futures.toArray(new CompletableFuture[futures.size()])); @@ -691,8 +705,16 @@ public class SpringSymbolIndex implements InitializingBean, SpringModelService { } @Override - public CompletableFuture> beans(String project) { - return CompletableFuture.completedFuture(Arrays.asList(springIndex.getBeansOfProject(project))); + public CompletableFuture> beans(String projectName) { + CompletableFuture latestTask = this.latestScheduledTaskByProject.get(projectName); + + if (latestTask != null) { + return latestTask.thenApply((e) -> Arrays.asList(springIndex.getBeansOfProject(projectName))); + } + else { + return CompletableFuture.completedFuture(null); + } + } /** @@ -782,8 +804,7 @@ public class SpringSymbolIndex implements InitializingBean, SpringModelService { CompletableFuture updateItem(IJavaProject project, DocumentDescriptor updatedDoc, String content, SpringIndexer indexer) { log.debug("scheduling updateItem {}. {}, {}, {}", project.getElementName(), updatedDoc.getDocURI(), updatedDoc.getLastModified(), indexer); - return CompletableFuture.runAsync(() -> { - + CompletableFuture future = CompletableFuture.runAsync(() -> { try { log.debug("updateItem {}. {}, {}, {}", project.getElementName(), updatedDoc.getDocURI(), updatedDoc.getLastModified(), indexer); indexer.updateFile(project, updatedDoc, content); @@ -791,6 +812,9 @@ public class SpringSymbolIndex implements InitializingBean, SpringModelService { log.error("{}", e); } }, this.updateQueue); + + this.latestScheduledTaskByProject.put(project.getElementName(), future); + return future; } CompletableFuture updateItems(IJavaProject project, DocumentDescriptor[] updatedDoc, SpringIndexer indexer) { @@ -798,7 +822,7 @@ public class SpringSymbolIndex implements InitializingBean, SpringModelService { log.debug("scheduling updateItem {}. {}, {}, {}", project.getElementName(), doc.getDocURI(), doc.getLastModified(), indexer); } - return CompletableFuture.runAsync(() -> { + CompletableFuture future = CompletableFuture.runAsync(() -> { try { for (DocumentDescriptor doc : updatedDoc) { @@ -810,6 +834,9 @@ public class SpringSymbolIndex implements InitializingBean, SpringModelService { log.error("{}", e); } }, this.updateQueue); + + this.latestScheduledTaskByProject.put(project.getElementName(), future); + return future; } private class DeleteItems implements Runnable { diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringModelServiceTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringModelServiceTest.java new file mode 100644 index 000000000..fad2f2071 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/metamodel/test/SpringModelServiceTest.java @@ -0,0 +1,91 @@ +/******************************************************************************* + * Copyright (c) 2023 VMware, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VMware, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.metamodel.test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.eclipse.lsp4j.Location; +import org.eclipse.lsp4j.Position; +import org.eclipse.lsp4j.Range; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; +import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; +import org.springframework.ide.vscode.boot.bootiful.SymbolProviderTestConf; +import org.springframework.ide.vscode.boot.index.SpringMetamodelIndex; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.protocol.spring.Bean; +import org.springframework.ide.vscode.commons.protocol.spring.InjectionPoint; +import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; +import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +/** + * @author Martin Lippert + */ +@ExtendWith(SpringExtension.class) +@BootLanguageServerTest +@Import(SymbolProviderTestConf.class) +public class SpringModelServiceTest { + + @Autowired private BootLanguageServerHarness harness; + @Autowired private JavaProjectFinder projectFinder; + @Autowired private SpringSymbolIndex indexer; + + private File directory; + + @BeforeEach + public void setup() throws Exception { + harness.intialize(null); + + directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spring-indexing/").toURI()); + + String projectDir = directory.toURI().toString(); + + // trigger project creation + projectFinder.find(new TextDocumentIdentifier(projectDir)).get(); + +// CompletableFuture initProject = indexer.waitOperation(); +// initProject.get(5, TimeUnit.SECONDS); + } + + @Test + void testSpringModelServiceBeansForNonExistingProject() throws Exception { + CompletableFuture> result = indexer.beans("random-project"); + + List list = result.get(); + assertNull(list); + } + + @Test + void testBeansNameAndTypeFromBeanAnnotatedMethod() throws Exception { + CompletableFuture> result = indexer.beans("test-spring-indexing"); + + List beans = result.get(5, TimeUnit.SECONDS); + + assertNotNull(beans); + assertEquals(11, beans.size()); + } + +}