From 40b622e4f540a87dd52d1b9970cfb8de2a905bf0 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Mon, 27 Jan 2025 15:26:27 +0100 Subject: [PATCH] add request mapping details as index child elements to controller bean --- .../RequestMappingIndexElement.java | 45 +++++++++++++++ .../RequestMappingSymbolProvider.java | 18 +++++- .../WebfluxHandlerMethodIndexElement.java | 32 +---------- .../RequestMappingSymbolProviderTest.java | 56 +++++++++++++++++-- .../java/org/test/SimpleMappingClass.java | 2 + ...rollerAsSubclassAndInterfaceHierarchy.java | 2 + 6 files changed, 119 insertions(+), 36 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingIndexElement.java diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingIndexElement.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingIndexElement.java new file mode 100644 index 000000000..f2ff26c47 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingIndexElement.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2025 Broadcom + * 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: + * Broadcom - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.requestmapping; + +import org.springframework.ide.vscode.commons.protocol.spring.AbstractSpringIndexElement; + +public class RequestMappingIndexElement extends AbstractSpringIndexElement { + + private final String path; + private final String[] httpMethods; + private final String[] contentTypes; + private final String[] acceptTypes; + + public RequestMappingIndexElement(String path, String[] httpMethods, String[] contentTypes, String[] acceptTypes) { + this.path = path; + this.httpMethods = httpMethods; + this.contentTypes = contentTypes; + this.acceptTypes = acceptTypes; + } + + public String getPath() { + return path; + } + + public String[] getHttpMethods() { + return httpMethods; + } + + public String[] getContentTypes() { + return contentTypes; + } + + public String[] getAcceptTypes() { + return acceptTypes; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java index 8205d8fb7..035f9f1fe 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/RequestMappingSymbolProvider.java @@ -33,7 +33,9 @@ import org.eclipse.lsp4j.Location; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ide.vscode.boot.java.Annotations; +import org.springframework.ide.vscode.boot.java.beans.CachedBean; import org.springframework.ide.vscode.boot.java.handlers.AbstractSymbolProvider; +import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation; import org.springframework.ide.vscode.boot.java.utils.ASTUtils; import org.springframework.ide.vscode.boot.java.utils.CachedSymbol; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaContext; @@ -69,8 +71,20 @@ public class RequestMappingSymbolProvider extends AbstractSymbolProvider { .filter(Objects::nonNull).map(p -> { return combinePath(parent, p); })) - .map(p -> RouteUtils.createRouteSymbol(location, p, methods, contentTypes, acceptTypes)) - .forEach((enhancedSymbol) -> context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), enhancedSymbol))); + .forEach(p -> { + // symbol + EnhancedSymbolInformation symbol = RouteUtils.createRouteSymbol(location, p, methods, contentTypes, acceptTypes); + context.getGeneratedSymbols().add(new CachedSymbol(context.getDocURI(), context.getLastModified(), symbol)); + + // index element for request mapping + List beans = context.getBeans(); + if (beans.size() > 0 ) { + CachedBean cachedBean = beans.get(beans.size() - 1); + if (cachedBean.getDocURI().equals(doc.getUri())) { + cachedBean.getBean().addChild(new RequestMappingIndexElement(p, methods, contentTypes, acceptTypes)); + } + } + }); } catch (Exception e) { log.error("problem occured while scanning for request mapping symbols from " + doc.getUri(), e); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerMethodIndexElement.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerMethodIndexElement.java index 5fac5da19..1d4dc0ca7 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerMethodIndexElement.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerMethodIndexElement.java @@ -10,26 +10,16 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.requestmapping; -import org.springframework.ide.vscode.commons.protocol.spring.AbstractSpringIndexElement; - -public class WebfluxHandlerMethodIndexElement extends AbstractSpringIndexElement { +public class WebfluxHandlerMethodIndexElement extends RequestMappingIndexElement { private final String handlerClass; private final String handlerMethod; - private final String path; - private final String[] httpMethods; - private final String[] contentTypes; - private final String[] acceptTypes; - public WebfluxHandlerMethodIndexElement(String handlerClass, String handlerMethod, String path, String[] httpMethods, String[] contentTypes, String[] acceptTypes) { + super(path, httpMethods, contentTypes, acceptTypes); + this.handlerClass = handlerClass; this.handlerMethod = handlerMethod; - - this.path = path; - this.httpMethods = httpMethods; - this.contentTypes = contentTypes; - this.acceptTypes = acceptTypes; } public String getHandlerClass() { @@ -40,20 +30,4 @@ public class WebfluxHandlerMethodIndexElement extends AbstractSpringIndexElement return handlerMethod; } - public String getPath() { - return path; - } - - public String[] getHttpMethods() { - return httpMethods; - } - - public String[] getContentTypes() { - return contentTypes; - } - - public String[] getAcceptTypes() { - return acceptTypes; - } - } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java index e230f0814..782a54485 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSymbolProviderTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017, 2024 Pivotal, Inc. + * Copyright (c) 2017, 2025 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 @@ -35,9 +35,13 @@ 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.boot.java.requestmapping.RequestMappingIndexElement; import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJavaDependencyTracker; import org.springframework.ide.vscode.boot.java.utils.test.TestFileScanListener; 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.SpringIndexElement; import org.springframework.ide.vscode.commons.util.UriUtil; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -58,6 +62,7 @@ public class RequestMappingSymbolProviderTest { @Autowired private BootLanguageServerHarness harness; @Autowired private JavaProjectFinder projectFinder; @Autowired private SpringSymbolIndex indexer; + @Autowired private SpringMetamodelIndex springIndex; private File directory; @@ -79,8 +84,24 @@ public class RequestMappingSymbolProviderTest { void testSimpleRequestMappingSymbol() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/SimpleMappingClass.java").toUri().toString(); List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/greeting", docUri, 6, 1, 6, 29)); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/greeting", docUri, 8, 1, 8, 29)); + } + + @Test + void testRequestMappingIndexElements() throws Exception { + Bean[] beans = springIndex.getBeansWithName("test-request-mapping-symbols", "simpleMappingClass"); + assertEquals(1, beans.length); + + List children = beans[0].getChildren(); + List mappingChildren = children.stream() + .filter(child -> child instanceof RequestMappingIndexElement) + .toList(); + + assertEquals(1, mappingChildren.size()); + + RequestMappingIndexElement mappingElement = (RequestMappingIndexElement) mappingChildren.get(0); + assertEquals("/greeting", mappingElement.getPath()); } @Test @@ -248,8 +269,33 @@ public class RequestMappingSymbolProviderTest { void testMappingPathFromSuperInterfaceEvenIfSuperclassContainsMappingPath() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/inheritance/ControllerAsSubclassAndInterfaceHierarchy.java").toUri().toString(); List symbols = indexer.getSymbols(docUri); - assertEquals(1, symbols.size()); - assertTrue(containsSymbol(symbols, "@/superinterface-path/last-path-segment -- GET - Accept: testconsume - Content-Type: text/plain", docUri, 6, 1, 6, 33)); + assertEquals(2, symbols.size()); + assertTrue(containsSymbol(symbols, "@/superinterface-path/last-path-segment -- GET - Accept: testconsume - Content-Type: text/plain", docUri, 8, 1, 8, 33)); + } + + @Test + void testMapoingIndexElementsWithDetails() throws Exception { + Bean[] beans = springIndex.getBeansWithName("test-request-mapping-symbols", "controllerAsSubclassAndInterfaceHierarchy"); + assertEquals(1, beans.length); + + List children = beans[0].getChildren(); + List mappingChildren = children.stream() + .filter(child -> child instanceof RequestMappingIndexElement) + .toList(); + + assertEquals(1, mappingChildren.size()); + + RequestMappingIndexElement mappingElement = (RequestMappingIndexElement) mappingChildren.get(0); + assertEquals("/superinterface-path/last-path-segment", mappingElement.getPath()); + + assertEquals(1, mappingElement.getHttpMethods().length); + assertEquals("GET", mappingElement.getHttpMethods()[0]); + + assertEquals(1, mappingElement.getAcceptTypes().length); + assertEquals("testconsume", mappingElement.getAcceptTypes()[0]); + + assertEquals(1, mappingElement.getContentTypes().length); + assertEquals("text/plain", mappingElement.getContentTypes()[0]); } @Test diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClass.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClass.java index dcedf4e30..65b1ca38d 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClass.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/SimpleMappingClass.java @@ -1,7 +1,9 @@ package org.test; +import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; +@Controller public class SimpleMappingClass { @RequestMapping("/greeting") diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/ControllerAsSubclassAndInterfaceHierarchy.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/ControllerAsSubclassAndInterfaceHierarchy.java index 0c11b65ef..9a123ce82 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/ControllerAsSubclassAndInterfaceHierarchy.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-symbols/src/main/java/org/test/inheritance/ControllerAsSubclassAndInterfaceHierarchy.java @@ -1,7 +1,9 @@ package org.test.inheritance; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +@RestController public class ControllerAsSubclassAndInterfaceHierarchy extends SuperclassWithMappingPath implements EmptyInterfaceWithinHierarchy { @GetMapping("last-path-segment")