From 3023bc22e8badb8990d627099dabdb42db4fdf3b Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Sun, 11 Mar 2018 08:24:26 +0100 Subject: [PATCH] further improvements to webflux symbol and code lens analysis, including content and accept type identification --- .../WebfluxAcceptTypeFinder.java | 22 +-- .../WebfluxContentTypeFinder.java | 15 +- .../WebfluxHandlerCodeLensProvider.java | 25 +-- .../WebfluxHandlerInformation.java | 26 +-- .../requestmapping/WebfluxMethodFinder.java | 14 +- .../WebfluxRouterSymbolProvider.java | 149 ++++++++++-------- .../java/requestmapping/WebfluxUtils.java | 32 +++- .../test/WebFluxCodeLensProviderTest.java | 38 ++++- .../WebFluxMappingSymbolProviderTest.java | 95 +++++++---- ...terExperiments.java => NestedRouter1.java} | 11 +- .../src/main/java/org/test/NestedRouter2.java | 35 ++++ .../{NestedRouter.java => NestedRouter3.java} | 4 +- ...PersonHandler.java => PersonHandler1.java} | 2 +- .../main/java/org/test/PersonHandler2.java | 22 +++ .../main/java/org/test/PersonHandler3.java | 22 +++ 15 files changed, 354 insertions(+), 158 deletions(-) rename headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/{RouterExperiments.java => NestedRouter1.java} (85%) create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/NestedRouter2.java rename headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/{NestedRouter.java => NestedRouter3.java} (94%) rename headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/{PersonHandler.java => PersonHandler1.java} (94%) create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/PersonHandler2.java create mode 100644 headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/PersonHandler3.java diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxAcceptTypeFinder.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxAcceptTypeFinder.java index f711b7281..c9b5dfa98 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxAcceptTypeFinder.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxAcceptTypeFinder.java @@ -10,6 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.requestmapping; +import java.util.LinkedHashSet; +import java.util.Set; + import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.MethodInvocation; @@ -19,32 +22,31 @@ import org.eclipse.jdt.core.dom.MethodInvocation; */ public class WebfluxAcceptTypeFinder extends ASTVisitor { - private String acceptType; + private Set acceptTypes; public WebfluxAcceptTypeFinder() { + this.acceptTypes = new LinkedHashSet<>(); } - public String getAcceptType() { - return acceptType; + public Set getAcceptTypes() { + return acceptTypes; } @Override public boolean visit(MethodInvocation node) { - boolean visitChildren = true; - IMethodBinding methodBinding = node.resolveMethodBinding(); if (WebfluxUtils.REQUEST_PREDICATES_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) { String name = methodBinding.getName(); if (name != null && WebfluxUtils.REQUEST_PREDICATE_ACCEPT_TYPE_METHOD.equals(name)) { - acceptType = WebfluxUtils.extractSimpleNameArgument(node); + String acceptType = WebfluxUtils.extractSimpleNameArgument(node); + if (acceptType != null) { + acceptTypes.add(acceptType); + } } } - if (WebfluxUtils.isRouteMethodInvocation(methodBinding)) { - visitChildren = false; - } - return visitChildren; + return !WebfluxUtils.isRouteMethodInvocation(methodBinding); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxContentTypeFinder.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxContentTypeFinder.java index e104c3a55..2e8cf56db 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxContentTypeFinder.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxContentTypeFinder.java @@ -10,6 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.requestmapping; +import java.util.LinkedHashSet; +import java.util.Set; + import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.IMethodBinding; @@ -20,15 +23,16 @@ import org.eclipse.jdt.core.dom.MethodInvocation; */ public class WebfluxContentTypeFinder extends ASTVisitor { - private String contentType; + private Set contentTypes; private ASTNode root; public WebfluxContentTypeFinder(ASTNode root) { this.root = root; + this.contentTypes = new LinkedHashSet<>(); } - public String getContentType() { - return contentType; + public Set getContentTypes() { + return contentTypes; } @Override @@ -41,7 +45,10 @@ public class WebfluxContentTypeFinder extends ASTVisitor { if (WebfluxUtils.REQUEST_PREDICATES_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) { String name = methodBinding.getName(); if (name != null && WebfluxUtils.REQUEST_PREDICATE_CONTENT_TYPE_METHOD.equals(name)) { - contentType = WebfluxUtils.extractSimpleNameArgument(node); + String contentType = WebfluxUtils.extractSimpleNameArgument(node); + if (contentType != null) { + contentTypes.add(contentType); + } } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java index 73950b090..fc14555d9 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerCodeLensProvider.java @@ -72,11 +72,16 @@ public class WebfluxHandlerCodeLensProvider implements CodeLensProvider { CodeLens codeLens = new CodeLens(); codeLens.setRange(document.toRange(node.getName().getStartPosition(), node.getName().getLength())); - String codeLensCommand = handlerInfo.getHttpMethod() != null ? handlerInfo.getHttpMethod() + " " : ""; + String httpMethod = WebfluxUtils.getStringRep(handlerInfo.getHttpMethods(), string -> string); + String codeLensCommand = httpMethod != null ? httpMethod + " " : ""; + codeLensCommand += handlerInfo.getPath(); + + String acceptType = WebfluxUtils.getStringRep(handlerInfo.getAcceptTypes(), WebfluxUtils::getMediaType); + codeLensCommand += acceptType != null ? " - Accept: " + acceptType : ""; - codeLensCommand += handlerInfo.getAcceptType() != null ? " - Accept: " + getMediaType(handlerInfo.getAcceptType()) : ""; - codeLensCommand += handlerInfo.getContentType() != null ? " - Content-Type: " + getMediaType(handlerInfo.getContentType()) : ""; + String contentType = WebfluxUtils.getStringRep(handlerInfo.getContentTypes(), WebfluxUtils::getMediaType); + codeLensCommand += contentType != null ? " - Content-Type: " + contentType : ""; codeLens.setCommand(new Command(codeLensCommand, null)); @@ -89,18 +94,4 @@ public class WebfluxHandlerCodeLensProvider implements CodeLensProvider { } } - protected String getMediaType(String handlerInfo) { - if (handlerInfo == null) { - return null; - } - - try { - MediaTypeMapping mediaType = MediaTypeMapping.valueOf(handlerInfo); - return mediaType.getMediaType(); - } - catch (IllegalArgumentException e) { - return handlerInfo; - } - } - } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerInformation.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerInformation.java index 7f7b34142..6e8c993df 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerInformation.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxHandlerInformation.java @@ -19,18 +19,18 @@ public class WebfluxHandlerInformation { private final String handlerMethod; private final String path; - private final String httpMethod; - private final String contentType; - private final String acceptType; + private final String[] httpMethods; + private final String[] contentTypes; + private final String[] acceptTypes; - public WebfluxHandlerInformation(String handlerClass, String handlerMethod, String path, String httpMethod, String contentType, String acceptType) { + public WebfluxHandlerInformation(String handlerClass, String handlerMethod, String path, String[] httpMethods, String[] contentTypes, String[] acceptTypes) { this.handlerClass = handlerClass; this.handlerMethod = handlerMethod; this.path = path; - this.httpMethod = httpMethod; - this.contentType = contentType; - this.acceptType = acceptType; + this.httpMethods = httpMethods; + this.contentTypes = contentTypes; + this.acceptTypes = acceptTypes; } public String getHandlerClass() { @@ -45,16 +45,16 @@ public class WebfluxHandlerInformation { return path; } - public String getHttpMethod() { - return httpMethod; + public String[] getHttpMethods() { + return httpMethods; } - public String getContentType() { - return contentType; + public String[] getContentTypes() { + return contentTypes; } - public String getAcceptType() { - return acceptType; + public String[] getAcceptTypes() { + return acceptTypes; } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxMethodFinder.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxMethodFinder.java index bef4fe7ba..57cbd1a54 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxMethodFinder.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxMethodFinder.java @@ -10,6 +10,9 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.requestmapping; +import java.util.LinkedHashSet; +import java.util.Set; + import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.IMethodBinding; @@ -20,15 +23,16 @@ import org.eclipse.jdt.core.dom.MethodInvocation; */ public class WebfluxMethodFinder extends ASTVisitor { - private String method; + private Set methods; private ASTNode root; public WebfluxMethodFinder(ASTNode root) { this.root = root; + this.methods = new LinkedHashSet<>(); } - public String getMethod() { - return method; + public Set getMethods() { + return methods; } @Override @@ -41,10 +45,10 @@ public class WebfluxMethodFinder extends ASTVisitor { if (WebfluxUtils.REQUEST_PREDICATES_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) { String name = methodBinding.getName(); if (name != null && WebfluxUtils.REQUEST_PREDICATE_HTTPMETHOD_METHODS.contains(name)) { - method = name; + methods.add(name); } else if (name != null && WebfluxUtils.REQUEST_PREDICATE_METHOD_METHOD.equals(name)) { - method = WebfluxUtils.extractQualifiedNameArgument(node); + methods.add(WebfluxUtils.extractQualifiedNameArgument(node)); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouterSymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouterSymbolProvider.java index d7394dab6..7dce4b632 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouterSymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxRouterSymbolProvider.java @@ -13,6 +13,7 @@ package org.springframework.ide.vscode.boot.java.requestmapping; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Set; import java.util.function.Function; import org.eclipse.jdt.core.dom.ASTNode; @@ -34,9 +35,6 @@ import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.TextDocument; -import reactor.util.function.Tuple2; -import reactor.util.function.Tuples; - /** * @author Martin Lippert */ @@ -92,10 +90,10 @@ public class WebfluxRouterSymbolProvider implements SymbolProvider { protected void extractMappingSymbol(MethodInvocation node, TextDocument doc, List result) { String path = extractPath(node); - String httpMethod = extractMethod(node); - String contentType = extractContentType(node); - String acceptType = extractAcceptType(node); + String[] httpMethods = extractMethods(node); + String[] contentTypes = extractContentTypes(node); + String[] acceptTypes = extractAcceptTypes(node); int methodNameStart = node.getName().getStartPosition(); int invocationStart = node.getStartPosition(); @@ -103,9 +101,10 @@ public class WebfluxRouterSymbolProvider implements SymbolProvider { if (path != null && path.length() > 0) { try { Location location = new Location(doc.getUri(), doc.toRange(methodNameStart, node.getLength() - (methodNameStart - invocationStart))); - String label = "@" + (path.startsWith("/") ? path : ("/" + path)) + (httpMethod == null || httpMethod.isEmpty() ? "" : " -- " + httpMethod); + String label = "@" + (path.startsWith("/") ? path : ("/" + path)); + label += (httpMethods == null || httpMethods.length == 0 ? "" : " -- " + WebfluxUtils.getStringRep(httpMethods, string -> string)); - WebfluxHandlerInformation handler = extractHandlerInformation(node, path, httpMethod, contentType, acceptType); + WebfluxHandlerInformation handler = extractHandlerInformation(node, path, httpMethods, contentTypes, acceptTypes); result.add(new EnhancedSymbolInformation(new SymbolInformation(label, SymbolKind.Interface, location), handler)); } catch (BadLocationException e) { @@ -118,98 +117,107 @@ public class WebfluxRouterSymbolProvider implements SymbolProvider { WebfluxPathFinder pathFinder = new WebfluxPathFinder(routerInvocation); routerInvocation.accept(pathFinder); - String path = pathFinder.getPath(); - if (path == null) path = ""; + List path = new ArrayList<>(); + String firstPath = pathFinder.getPath(); + if (firstPath != null) { + path.add(firstPath); + } - return extractNestedValue(routerInvocation, path, (methodInvocationPathPrefix) -> { - IMethodBinding methodBinding = methodInvocationPathPrefix.getT1().resolveMethodBinding(); + extractNestedValue(routerInvocation, path, (methodInvocation) -> { + IMethodBinding methodBinding = methodInvocation.resolveMethodBinding(); String methodName = methodBinding.getName(); if (WebfluxUtils.REQUEST_PREDICATE_PATH_METHOD.equals(methodName)) { - String additionalPath = WebfluxUtils.extractStringLiteralArgument(methodInvocationPathPrefix.getT1()); + String additionalPath = WebfluxUtils.extractStringLiteralArgument(methodInvocation); if (additionalPath != null && additionalPath.length() > 0) { - return additionalPath + methodInvocationPathPrefix.getT2(); + return additionalPath; } } - - return methodInvocationPathPrefix.getT2(); + return null; }); + + StringBuilder result = new StringBuilder(); + path.stream().forEach(part -> result.insert(0, part)); + + return result.toString(); + } - private String extractMethod(MethodInvocation routerInvocation) { + private String[] extractMethods(MethodInvocation routerInvocation) { WebfluxMethodFinder methodFinder = new WebfluxMethodFinder(routerInvocation); - routerInvocation.accept(methodFinder); - - String method = methodFinder.getMethod(); - - return extractNestedValue(routerInvocation, method, (methodInvocationPathPrefix) -> { - IMethodBinding methodBinding = methodInvocationPathPrefix.getT1().resolveMethodBinding(); - String methodName = methodBinding.getName(); - - if (WebfluxUtils.REQUEST_PREDICATE_METHOD_METHOD.equals(methodName)) { - String newMethod = WebfluxUtils.extractStringLiteralArgument(methodInvocationPathPrefix.getT1()); - if (method == null) { - return newMethod; - } - } - - return methodInvocationPathPrefix.getT2(); - }); - } - - private String extractAcceptType(MethodInvocation routerInvocation) { - String acceptType = null; - - WebfluxAcceptTypeFinder acceptTypeFinder = new WebfluxAcceptTypeFinder(); List arguments = routerInvocation.arguments(); for (Object argument : arguments) { if (argument != null && argument instanceof ASTNode) { - ((ASTNode)argument).accept(acceptTypeFinder); - if (acceptTypeFinder.getAcceptType() != null) { - acceptType = acceptTypeFinder.getAcceptType(); - } + ((ASTNode)argument).accept(methodFinder); + } + } + + final Set methods = methodFinder.getMethods(); + + extractNestedValue(routerInvocation, methods, (methodInvocation) -> { + IMethodBinding methodBinding = methodInvocation.resolveMethodBinding(); + String methodName = methodBinding.getName(); + + if (WebfluxUtils.REQUEST_PREDICATE_METHOD_METHOD.equals(methodName)) { + return WebfluxUtils.extractStringLiteralArgument(methodInvocation); + } + return null; + }); + + return (String[]) methods.toArray(new String[methods.size()]); + } + + private String[] extractAcceptTypes(MethodInvocation routerInvocation) { + WebfluxAcceptTypeFinder typeFinder = new WebfluxAcceptTypeFinder(); + List arguments = routerInvocation.arguments(); + for (Object argument : arguments) { + if (argument != null && argument instanceof ASTNode) { + ((ASTNode)argument).accept(typeFinder); } } - return extractNestedValue(routerInvocation, acceptType, (methodInvocationPathPrefix) -> { - IMethodBinding methodBinding = methodInvocationPathPrefix.getT1().resolveMethodBinding(); + Set acceptTypes = typeFinder.getAcceptTypes(); + + extractNestedValue(routerInvocation, acceptTypes, (methodInvocation) -> { + IMethodBinding methodBinding = methodInvocation.resolveMethodBinding(); String methodName = methodBinding.getName(); if (WebfluxUtils.REQUEST_PREDICATE_ACCEPT_TYPE_METHOD.equals(methodName)) { - String newAcceptType = WebfluxUtils.extractSimpleNameArgument(methodInvocationPathPrefix.getT1()); - if (newAcceptType != null) { - return newAcceptType; - } + return WebfluxUtils.extractSimpleNameArgument(methodInvocation); } - - return methodInvocationPathPrefix.getT2(); + return null; }); + + return (String[]) acceptTypes.toArray(new String[acceptTypes.size()]); } - private String extractContentType(MethodInvocation routerInvocation) { + private String[] extractContentTypes(MethodInvocation routerInvocation) { WebfluxContentTypeFinder contentTypeFinder = new WebfluxContentTypeFinder(routerInvocation); - routerInvocation.accept(contentTypeFinder); + List arguments = routerInvocation.arguments(); + for (Object argument : arguments) { + if (argument != null && argument instanceof ASTNode) { + ((ASTNode)argument).accept(contentTypeFinder); + } + } - String contentType = contentTypeFinder.getContentType(); + Set contentTypes = contentTypeFinder.getContentTypes(); - return extractNestedValue(routerInvocation, contentType, (methodInvocationPathPrefix) -> { - IMethodBinding methodBinding = methodInvocationPathPrefix.getT1().resolveMethodBinding(); + extractNestedValue(routerInvocation, contentTypes, (methodInvocation) -> { + IMethodBinding methodBinding = methodInvocation.resolveMethodBinding(); String methodName = methodBinding.getName(); if (WebfluxUtils.REQUEST_PREDICATE_CONTENT_TYPE_METHOD.equals(methodName)) { - String newContentType = WebfluxUtils.extractSimpleNameArgument(methodInvocationPathPrefix.getT1()); - if (contentType == null) { - return newContentType; - } + return WebfluxUtils.extractSimpleNameArgument(methodInvocation); } - - return methodInvocationPathPrefix.getT2(); + return null; }); + + return (String[]) contentTypes.toArray(new String[contentTypes.size()]); } - private String extractNestedValue(ASTNode node, String value, Function, String> extractor) { + private void extractNestedValue(ASTNode node, Collection values, Function extractor) { if (node == null || node instanceof TypeDeclaration) { - return value; + return; } if (node instanceof MethodInvocation) { @@ -223,17 +231,20 @@ public class WebfluxRouterSymbolProvider implements SymbolProvider { for (Object argument : arguments) { if (argument instanceof MethodInvocation) { MethodInvocation nestedMethod = (MethodInvocation) argument; - value = extractor.apply(Tuples.of(nestedMethod, value)); + String value = extractor.apply(nestedMethod); + if (value != null) { + values.add(value); + } } } } } } - return extractNestedValue(node.getParent(), value, extractor); + extractNestedValue(node.getParent(), values, extractor); } - private WebfluxHandlerInformation extractHandlerInformation(MethodInvocation node, String path, String httpMethod, String contentType, String acceptType) { + private WebfluxHandlerInformation extractHandlerInformation(MethodInvocation node, String path, String[] httpMethods, String[] contentTypes, String[] acceptTypes) { List arguments = node.arguments(); if (arguments != null) { @@ -249,7 +260,7 @@ public class WebfluxRouterSymbolProvider implements SymbolProvider { String handlerMethod = methodBinding.getMethodDeclaration().toString(); if (handlerMethod != null) handlerMethod = handlerMethod.trim(); - return new WebfluxHandlerInformation(handlerClass, handlerMethod, path, httpMethod, contentType, acceptType); + return new WebfluxHandlerInformation(handlerClass, handlerMethod, path, httpMethods, contentTypes, acceptTypes); } } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxUtils.java index 51c1a7a1f..b8b11ba91 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxUtils.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/requestmapping/WebfluxUtils.java @@ -14,6 +14,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.Function; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.MethodInvocation; @@ -80,8 +81,6 @@ public class WebfluxUtils { return null; } - - public static boolean isRouteMethodInvocation(IMethodBinding methodBinding) { if (ROUTER_FUNCTIONS_TYPE.equals(methodBinding.getDeclaringClass().getBinaryName())) { String name = methodBinding.getName(); @@ -97,8 +96,35 @@ public class WebfluxUtils { } return false; - } + public static String getMediaType(String constantRep) { + if (constantRep == null) { + return null; + } + + try { + MediaTypeMapping mediaType = MediaTypeMapping.valueOf(constantRep); + return mediaType.getMediaType(); + } + catch (IllegalArgumentException e) { + return constantRep; + } + } + + public static String getStringRep(String[] multipleTypes, Function valueConverter) { + if (multipleTypes == null || multipleTypes.length == 0) return null; + + StringBuilder result = new StringBuilder(valueConverter.apply(multipleTypes[0])); + for (int i = 1; i < multipleTypes.length; i++) { + result.append(", "); + result.append(valueConverter.apply(multipleTypes[i])); + } + + return result.toString(); + } + + + } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxCodeLensProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxCodeLensProviderTest.java index d9a8a2a9a..27d91e82c 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxCodeLensProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxCodeLensProviderTest.java @@ -41,7 +41,7 @@ public class WebFluxCodeLensProviderTest { } @Test - public void testRoutesCodeLenses() throws Exception { + public void testRoutesCodeLensesSimpleCase() throws Exception { harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-webflux-project/").toURI())); File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-webflux-project/").toURI()); @@ -59,6 +59,42 @@ public class WebFluxCodeLensProviderTest { assertTrue(containsCodeLens(codeLenses, "GET /quotes - Accept: application/json", 41, 29, 41, 40)); } + @Test + public void testRoutesCodeLensesNestedRoutes1() throws Exception { + harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-webflux-project/").toURI())); + File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-webflux-project/").toURI()); + + String docUri = directory.toPath().resolve("src/main/java/org/test/PersonHandler1.java").toUri().toString(); + TextDocumentInfo doc = harness.getOrReadFile(new File(new URI(docUri)), LanguageId.JAVA.toString()); + TextDocumentInfo openedDoc = harness.openDocument(doc); + + List codeLenses = harness.getCodeLenses(openedDoc); + + assertEquals(3, codeLenses.size()); + + assertTrue(containsCodeLens(codeLenses, "GET /person/{id} - Accept: application/json", 9, 29, 9, 38)); + assertTrue(containsCodeLens(codeLenses, "POST /person/ - Content-Type: application/json", 13, 29, 13, 41)); + assertTrue(containsCodeLens(codeLenses, "GET /person - Accept: application/json", 17, 29, 17, 39)); + } + + @Test + public void testRoutesCodeLensesNestedRoutes2() throws Exception { + harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-webflux-project/").toURI())); + File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-webflux-project/").toURI()); + + String docUri = directory.toPath().resolve("src/main/java/org/test/PersonHandler2.java").toUri().toString(); + TextDocumentInfo doc = harness.getOrReadFile(new File(new URI(docUri)), LanguageId.JAVA.toString()); + TextDocumentInfo openedDoc = harness.openDocument(doc); + + List codeLenses = harness.getCodeLenses(openedDoc); + + assertEquals(3, codeLenses.size()); + + assertTrue(containsCodeLens(codeLenses, "GET /person/{id} - Accept: application/json", 9, 29, 9, 38)); + assertTrue(containsCodeLens(codeLenses, "POST / - Accept: application/json - Content-Type: application/json, application/pdf", 13, 29, 13, 41)); + assertTrue(containsCodeLens(codeLenses, "GET, HEAD /person - Accept: text/plain, application/json", 17, 29, 17, 39)); + } + private boolean containsCodeLens(List codeLenses, String commandTitle, int startLine, int startPosition, int endLine, int endPosition) { for (CodeLens codeLens : codeLenses) { Command command = codeLens.getCommand(); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java index 4d6221074..3acb1d4c5 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/WebFluxMappingSymbolProviderTest.java @@ -15,6 +15,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.File; +import java.util.Arrays; import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; @@ -71,43 +72,43 @@ public class WebFluxMappingSymbolProviderTest { WebfluxHandlerInformation handlerInfo1 = getWebfluxHandler(addons, "/hello", "GET").get(0); assertEquals("/hello", handlerInfo1.getPath()); - assertEquals("GET", handlerInfo1.getHttpMethod()); - assertNull(handlerInfo1.getContentType()); - assertEquals("TEXT_PLAIN", handlerInfo1.getAcceptType()); + assertEquals("[GET]", Arrays.toString(handlerInfo1.getHttpMethods())); + assertEquals(0, handlerInfo1.getContentTypes().length); + assertEquals("[TEXT_PLAIN]", Arrays.toString(handlerInfo1.getAcceptTypes())); assertEquals("org.test.QuoteHandler", handlerInfo1.getHandlerClass()); assertEquals("public Mono hello(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo1.getHandlerMethod()); WebfluxHandlerInformation handlerInfo2 = getWebfluxHandler(addons, "/echo", "POST").get(0); assertEquals("/echo", handlerInfo2.getPath()); - assertEquals("POST", handlerInfo2.getHttpMethod()); - assertEquals("TEXT_PLAIN", handlerInfo2.getContentType()); - assertEquals("TEXT_PLAIN", handlerInfo2.getAcceptType()); + assertEquals("[POST]", Arrays.toString(handlerInfo2.getHttpMethods())); + assertEquals("[TEXT_PLAIN]", Arrays.toString(handlerInfo2.getContentTypes())); + assertEquals("[TEXT_PLAIN]", Arrays.toString(handlerInfo2.getAcceptTypes())); assertEquals("org.test.QuoteHandler", handlerInfo2.getHandlerClass()); assertEquals("public Mono echo(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo2.getHandlerMethod()); WebfluxHandlerInformation handlerInfo3 = getWebfluxHandler(addons, "/quotes", "GET").get(0); assertEquals("/quotes", handlerInfo3.getPath()); - assertEquals("GET", handlerInfo3.getHttpMethod()); - assertNull(handlerInfo3.getContentType()); - assertEquals("APPLICATION_STREAM_JSON", handlerInfo3.getAcceptType()); + assertEquals("[GET]", Arrays.toString(handlerInfo3.getHttpMethods())); + assertEquals(0, handlerInfo3.getContentTypes().length); + assertEquals("[APPLICATION_STREAM_JSON]", Arrays.toString(handlerInfo3.getAcceptTypes())); assertEquals("org.test.QuoteHandler", handlerInfo3.getHandlerClass()); assertEquals("public Mono streamQuotes(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo3.getHandlerMethod()); WebfluxHandlerInformation handlerInfo4 = getWebfluxHandler(addons, "/quotes", "GET").get(1); assertEquals("/quotes", handlerInfo4.getPath()); - assertEquals("GET", handlerInfo4.getHttpMethod()); - assertNull(handlerInfo4.getContentType()); - assertEquals("APPLICATION_JSON", handlerInfo4.getAcceptType()); + assertEquals("[GET]", Arrays.toString(handlerInfo4.getHttpMethods())); + assertEquals(0, handlerInfo4.getContentTypes().length); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo4.getAcceptTypes())); assertEquals("org.test.QuoteHandler", handlerInfo4.getHandlerClass()); assertEquals("public Mono fetchQuotes(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo4.getHandlerMethod()); } @Test - public void testNestedRoutesMappingSymbols() throws Exception { + public void testNestedRoutesMappingSymbols1() throws Exception { harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-webflux-project/").toURI())); File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-webflux-project/").toURI()); - String docUri = directory.toPath().resolve("src/main/java/org/test/NestedRouter.java").toUri().toString(); + String docUri = directory.toPath().resolve("src/main/java/org/test/NestedRouter1.java").toUri().toString(); List symbols = getSymbols(docUri); assertEquals(5, symbols.size()); assertTrue(containsSymbol(symbols, "@/person/{id} -- GET", docUri, 27, 6, 27, 45)); @@ -119,26 +120,66 @@ public class WebFluxMappingSymbolProviderTest { WebfluxHandlerInformation handlerInfo1 = getWebfluxHandler(addons, "/person/{id}", "GET").get(0); assertEquals("/person/{id}", handlerInfo1.getPath()); - assertEquals("GET", handlerInfo1.getHttpMethod()); - assertNull(handlerInfo1.getContentType()); - assertEquals("APPLICATION_JSON", handlerInfo1.getAcceptType()); - assertEquals("org.test.PersonHandler", handlerInfo1.getHandlerClass()); + assertEquals("[GET]", Arrays.toString(handlerInfo1.getHttpMethods())); + assertEquals(0, handlerInfo1.getContentTypes().length); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo1.getAcceptTypes())); + assertEquals("org.test.PersonHandler1", handlerInfo1.getHandlerClass()); assertEquals("public Mono getPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo1.getHandlerMethod()); WebfluxHandlerInformation handlerInfo2 = getWebfluxHandler(addons, "/person/", "POST").get(0); assertEquals("/person/", handlerInfo2.getPath()); - assertEquals("POST", handlerInfo2.getHttpMethod()); - assertEquals("APPLICATION_JSON", handlerInfo2.getContentType()); - assertNull(handlerInfo2.getAcceptType()); - assertEquals("org.test.PersonHandler", handlerInfo2.getHandlerClass()); + assertEquals("[POST]", Arrays.toString(handlerInfo2.getHttpMethods())); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo2.getContentTypes())); + assertEquals(0, handlerInfo2.getAcceptTypes().length); + assertEquals("org.test.PersonHandler1", handlerInfo2.getHandlerClass()); assertEquals("public Mono createPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo2.getHandlerMethod()); WebfluxHandlerInformation handlerInfo3 = getWebfluxHandler(addons, "/person", "GET").get(0); assertEquals("/person", handlerInfo3.getPath()); - assertEquals("GET", handlerInfo3.getHttpMethod()); - assertNull(handlerInfo3.getContentType()); - assertEquals("APPLICATION_JSON", handlerInfo3.getAcceptType()); - assertEquals("org.test.PersonHandler", handlerInfo3.getHandlerClass()); + assertEquals("[GET]", Arrays.toString(handlerInfo3.getHttpMethods())); + assertEquals(0, handlerInfo3.getContentTypes().length); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo3.getAcceptTypes())); + assertEquals("org.test.PersonHandler1", handlerInfo3.getHandlerClass()); + assertEquals("public Mono listPeople(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo3.getHandlerMethod()); + } + + @Test + public void testNestedRoutesMappingSymbols2() throws Exception { + harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-webflux-project/").toURI())); + File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-webflux-project/").toURI()); + + String docUri = directory.toPath().resolve("src/main/java/org/test/NestedRouter2.java").toUri().toString(); + List symbols = getSymbols(docUri); + assertEquals(5, symbols.size()); + assertTrue(containsSymbol(symbols, "@/person/{id} -- GET", docUri, 29, 6, 29, 45)); + assertTrue(containsSymbol(symbols, "@/ -- POST", docUri, 31, 6, 31, 117)); + assertTrue(containsSymbol(symbols, "@/person -- GET, HEAD", docUri, 30, 7, 30, 113)); + + List addons = getAdditionalInformation(docUri); + assertEquals(3, addons.size()); + + WebfluxHandlerInformation handlerInfo1 = getWebfluxHandler(addons, "/person/{id}", "GET").get(0); + assertEquals("/person/{id}", handlerInfo1.getPath()); + assertEquals("[GET]", Arrays.toString(handlerInfo1.getHttpMethods())); + assertEquals(0, handlerInfo1.getContentTypes().length); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo1.getAcceptTypes())); + assertEquals("org.test.PersonHandler2", handlerInfo1.getHandlerClass()); + assertEquals("public Mono getPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo1.getHandlerMethod()); + + WebfluxHandlerInformation handlerInfo2 = getWebfluxHandler(addons, "/", "POST").get(0); + assertEquals("/", handlerInfo2.getPath()); + assertEquals("[POST]", Arrays.toString(handlerInfo2.getHttpMethods())); + assertEquals("[APPLICATION_JSON, APPLICATION_PDF]", Arrays.toString(handlerInfo2.getContentTypes())); + assertEquals("[APPLICATION_JSON]", Arrays.toString(handlerInfo2.getAcceptTypes())); + assertEquals("org.test.PersonHandler2", handlerInfo2.getHandlerClass()); + assertEquals("public Mono createPerson(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo2.getHandlerMethod()); + + WebfluxHandlerInformation handlerInfo3 = getWebfluxHandler(addons, "/person", "HEAD").get(0); + assertEquals("/person", handlerInfo3.getPath()); + assertEquals("[GET, HEAD]", Arrays.toString(handlerInfo3.getHttpMethods())); + assertEquals(0, handlerInfo3.getContentTypes().length); + assertEquals("[TEXT_PLAIN, APPLICATION_JSON]", Arrays.toString(handlerInfo3.getAcceptTypes())); + assertEquals("org.test.PersonHandler2", handlerInfo3.getHandlerClass()); assertEquals("public Mono listPeople(org.springframework.web.reactive.function.server.ServerRequest)", handlerInfo3.getHandlerMethod()); } @@ -171,7 +212,7 @@ public class WebFluxMappingSymbolProviderTest { return addons.stream() .filter((obj) -> obj instanceof WebfluxHandlerInformation) .map((obj -> (WebfluxHandlerInformation) obj)) - .filter((addon) -> addon.getPath().equals(path) && addon.getHttpMethod().equals(httpMethod)) + .filter((addon) -> addon.getPath().equals(path) && Arrays.asList(addon.getHttpMethods()).contains(httpMethod)) .collect(Collectors.toList()); } diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/RouterExperiments.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/NestedRouter1.java similarity index 85% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/RouterExperiments.java rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/NestedRouter1.java index 5634bed69..e409ee4bb 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/RouterExperiments.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/NestedRouter1.java @@ -17,18 +17,17 @@ import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerResponse; @Configuration -public class RouterExperiments { +public class NestedRouter1 { @Bean - public RouterFunction superRoutingFunction() { - PersonHandler handler = new PersonHandler(); + public RouterFunction routingFunction1() { + PersonHandler1 handler = new PersonHandler1(); - return nest(path("/super"), - nest(path("/something"), + return nest(path("/person"), nest(accept(APPLICATION_JSON), route(GET("/{id}"), handler::getPerson) .andRoute(method(HttpMethod.GET), handler::listPeople) - ).andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::createPerson))); + ).andRoute(POST("/").and(contentType(APPLICATION_JSON)), handler::createPerson)); } } diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/NestedRouter2.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/NestedRouter2.java new file mode 100644 index 000000000..83ccffcf9 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/NestedRouter2.java @@ -0,0 +1,35 @@ +package org.test; + +import static org.springframework.http.MediaType.APPLICATION_JSON; +import static org.springframework.http.MediaType.APPLICATION_PDF; +import static org.springframework.http.MediaType.TEXT_PLAIN; +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RequestPredicates.POST; +import static org.springframework.web.reactive.function.server.RequestPredicates.accept; +import static org.springframework.web.reactive.function.server.RequestPredicates.contentType; +import static org.springframework.web.reactive.function.server.RequestPredicates.method; +import static org.springframework.web.reactive.function.server.RequestPredicates.path; +import static org.springframework.web.reactive.function.server.RouterFunctions.nest; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; + +@Configuration +public class NestedRouter2 { + + @Bean + public RouterFunction routingFunction2() { + PersonHandler2 handler = new PersonHandler2(); + + return nest(accept(APPLICATION_JSON), + nest(path("/person"), + route(GET("/{id}"), handler::getPerson) + .andRoute(method(HttpMethod.GET).and(method(HttpMethod.HEAD)).and(accept(TEXT_PLAIN)), handler::listPeople) + ).andRoute(POST("/").and(contentType(APPLICATION_JSON)).and(contentType(APPLICATION_PDF)), handler::createPerson)); + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/NestedRouter.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/NestedRouter3.java similarity index 94% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/NestedRouter.java rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/NestedRouter3.java index 89d063b17..3abe41fbe 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/NestedRouter.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/NestedRouter3.java @@ -17,11 +17,11 @@ import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.ServerResponse; @Configuration -public class NestedRouter { +public class NestedRouter3 { @Bean public RouterFunction routingFunction() { - PersonHandler handler = new PersonHandler(); + PersonHandler3 handler = new PersonHandler3(); return nest(path("/person"), nest(accept(APPLICATION_JSON), diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/PersonHandler.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/PersonHandler1.java similarity index 94% rename from headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/PersonHandler.java rename to headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/PersonHandler1.java index a1bdb96b4..73ceec1e6 100644 --- a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/PersonHandler.java +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/PersonHandler1.java @@ -5,7 +5,7 @@ import org.springframework.web.reactive.function.server.ServerResponse; import reactor.core.publisher.Mono; -public class PersonHandler { +public class PersonHandler1 { public Mono getPerson(ServerRequest request) { return ServerResponse.notFound().build(); diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/PersonHandler2.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/PersonHandler2.java new file mode 100644 index 000000000..d4d60aecc --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/PersonHandler2.java @@ -0,0 +1,22 @@ +package org.test; + +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; + +import reactor.core.publisher.Mono; + +public class PersonHandler2 { + + public Mono getPerson(ServerRequest request) { + return ServerResponse.notFound().build(); + } + + public Mono createPerson(ServerRequest request) { + return ServerResponse.notFound().build(); + } + + public Mono listPeople(ServerRequest request) { + return ServerResponse.notFound().build(); + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/PersonHandler3.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/PersonHandler3.java new file mode 100644 index 000000000..b1ec76362 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-webflux-project/src/main/java/org/test/PersonHandler3.java @@ -0,0 +1,22 @@ +package org.test; + +import org.springframework.web.reactive.function.server.ServerRequest; +import org.springframework.web.reactive.function.server.ServerResponse; + +import reactor.core.publisher.Mono; + +public class PersonHandler3 { + + public Mono getPerson(ServerRequest request) { + return ServerResponse.notFound().build(); + } + + public Mono createPerson(ServerRequest request) { + return ServerResponse.notFound().build(); + } + + public Mono listPeople(ServerRequest request) { + return ServerResponse.notFound().build(); + } + +}