Refine request mappings

This commit is contained in:
BoykoAlex
2017-10-26 11:43:16 -04:00
parent c614d6059e
commit 29c2a6e2b7
12 changed files with 1244 additions and 193 deletions

View File

@@ -11,15 +11,15 @@
package org.springframework.ide.vscode.boot.java.requestmapping;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import org.eclipse.lsp4j.Location;
import org.eclipse.lsp4j.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.SymbolKind;
import org.json.JSONObject;
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.util.Log;
@@ -44,7 +44,12 @@ public class LiveAppURLSymbolProvider {
SpringBootApp[] runningApps = runningAppProvider.getAllRunningSpringApps().toArray(new SpringBootApp[0]);
for (SpringBootApp app : runningApps) {
try {
collectLiveAppSymbols(result, app);
String host = app.getHost();
String port = app.getPort();
Stream<String> urls = app.getRequestMappings().stream()
.flatMap(rm -> Arrays.stream(rm.getSplitPath()))
.map(path -> UrlUtil.createUrl(host, port, path));
urls.forEach(url -> result.add(new SymbolInformation(url, SymbolKind.Method, new Location(url, new Range(new Position(0, 0), new Position(0, 1))))));
}
catch (Exception e) {
Log.log(e);
@@ -57,21 +62,4 @@ public class LiveAppURLSymbolProvider {
return result;
}
private void collectLiveAppSymbols(List<SymbolInformation> result, SpringBootApp app) throws Exception {
String mappings = app.getRequestMappings();
JSONObject requestMappings = new JSONObject(mappings);
Iterator<String> keys = requestMappings.keys();
while (keys.hasNext()) {
String key = keys.next();
String extractedPath = UrlUtil.extractPath(key);
if (extractedPath != null) {
String[] splitPath = UrlUtil.splitPath(extractedPath);
for (String path : splitPath) {
String url = UrlUtil.createUrl(app.getHost(), app.getPort(), path);
result.add(new SymbolInformation(url, SymbolKind.Method, new Location(url, new Range(new Position(0, 0), new Position(0, 1)))));
}
}
}
}
}

View File

@@ -11,20 +11,27 @@
package org.springframework.ide.vscode.boot.java.requestmapping;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MemberValuePair;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.TypeDeclaration;
@@ -32,17 +39,18 @@ import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.MarkedString;
import org.eclipse.lsp4j.Range;
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.java.parser.JLRMethodParser;
import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser.JLRMethod;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.Log;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.google.common.collect.ImmutableList;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
/**
* @author Martin Lippert
*/
@@ -58,7 +66,7 @@ public class RequestMappingHoverProvider implements HoverProvider {
public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
try {
if (runningApps.length > 0) {
Optional<RequestMappingMethod> val = getRequestMappingMethodFromRunningApp(annotation, runningApps);
Optional<Tuple2<RequestMapping, SpringBootApp>> val = getRequestMappingMethodFromRunningApp(annotation, runningApps);
if (val.isPresent()) {
Range hoverRange = doc.toRange(annotation.getStartPosition(), annotation.getLength());
return ImmutableList.of(hoverRange);
@@ -77,7 +85,7 @@ public class RequestMappingHoverProvider implements HoverProvider {
try {
List<Either<String, MarkedString>> hoverContent = new ArrayList<>();
Optional<RequestMappingMethod> val = getRequestMappingMethodFromRunningApp(annotation, runningApps);
Optional<Tuple2<RequestMapping, SpringBootApp>> val = getRequestMappingMethodFromRunningApp(annotation, runningApps);
if (val.isPresent()) {
addHoverContent(val.get(), hoverContent);
@@ -97,25 +105,18 @@ public class RequestMappingHoverProvider implements HoverProvider {
return null;
}
private Optional<RequestMappingMethod> getRequestMappingMethodFromRunningApp(Annotation annotation,
private Optional<Tuple2<RequestMapping, SpringBootApp>> getRequestMappingMethodFromRunningApp(Annotation annotation,
SpringBootApp[] runningApps) {
try {
for (SpringBootApp app : runningApps) {
String mappings = app.getRequestMappings();
if (mappings!=null) {
JSONObject requestMappings = new JSONObject(mappings);
String rawPath = getRawPath(annotation, requestMappings);
if (rawPath != null) {
String path = UrlUtil.extractPath(rawPath);
if (path != null) {
String rawMethod = getRawMethod(annotation, requestMappings);
JLRMethod parsedMethod = JLRMethodParser.parse(rawMethod);
if (methodMatchesAnnotation(annotation, parsedMethod)) {
return Optional.of(new RequestMappingMethod(path, parsedMethod, app));
}
}
}
Collection<RequestMapping> mappings = app.getRequestMappings();
if (mappings!=null && !mappings.isEmpty()) {
return mappings.stream()
.filter(rm -> matchesAnnotation(annotation, rm))
.filter(rm -> methodMatchesAnnotation(annotation, rm))
.map(rm -> Tuples.of(rm, app))
.findFirst();
}
}
} catch (Exception e) {
@@ -124,16 +125,19 @@ public class RequestMappingHoverProvider implements HoverProvider {
return Optional.empty();
}
private boolean methodMatchesAnnotation(Annotation annotation, JLRMethod requestMappingMethod) {
String rqClassName = requestMappingMethod.getFQClassName();
String rqMethod = requestMappingMethod.getMethodName();
private boolean methodMatchesAnnotation(Annotation annotation, RequestMapping rm) {
String rqClassName = rm.getFullyQualifiedClassName();
ASTNode parent = annotation.getParent();
if (parent instanceof MethodDeclaration) {
MethodDeclaration methodDec = (MethodDeclaration) parent;
IMethodBinding binding = methodDec.resolveBinding();
return binding.getDeclaringClass().getQualifiedName().equals(rqClassName) &&
binding.getName().equals(rqMethod);
return binding.getDeclaringClass().getQualifiedName().equals(rqClassName)
&& binding.getName().equals(rm.getMethodName())
&& Arrays.equals(Arrays.stream(binding.getParameterTypes())
.map(t -> t.getQualifiedName())
.toArray(String[]::new),
rm.getMethodParameters());
} else if (parent instanceof TypeDeclaration) {
TypeDeclaration typeDec = (TypeDeclaration) parent;
return typeDec.resolveBinding().getQualifiedName().equals(rqClassName);
@@ -141,113 +145,156 @@ public class RequestMappingHoverProvider implements HoverProvider {
return false;
}
private void addHoverContent(RequestMappingMethod mappingMethod, List<Either<String, MarkedString>> hoverContent) throws Exception {
String processId = mappingMethod.app.getProcessID();
String processName = mappingMethod.app.getProcessName();
String path = mappingMethod.requestMappingPath;
private void addHoverContent(Tuple2<RequestMapping, SpringBootApp> mappingMethod, List<Either<String, MarkedString>> hoverContent) throws Exception {
String processId = mappingMethod.getT2().getProcessID();
String processName = mappingMethod.getT2().getProcessName();
String port = mappingMethod.getT2().getPort();
String host = mappingMethod.getT2().getHost();
StringBuilder builder = new StringBuilder();
String port = mappingMethod.app.getPort();
String host = mappingMethod.app.getHost();
String url = UrlUtil.createUrl(host, port, path);
Arrays.stream(mappingMethod.getT1().getSplitPath()).forEach(path -> {
builder.append("Path: ");
builder.append("[");
builder.append(path);
builder.append("]");
builder.append("(");
builder.append(url);
builder.append(")");
String url = UrlUtil.createUrl(host, port, path);
if (builder.length() > 0) {
builder.append("\n");
}
builder.append("Path: ");
builder.append("[");
builder.append(path);
builder.append("]");
builder.append("(");
builder.append(url);
builder.append(")");
});
hoverContent.add(Either.forLeft(builder.toString()));
hoverContent.add(Either.forLeft("Process ID: " + processId));
hoverContent.add(Either.forLeft("Process Name: " + processName));
}
private String getRawMethod(Annotation annotation, JSONObject mappings) {
Iterator<String> keys = mappings.keys();
while (keys.hasNext()) {
String key = keys.next();
if (matchesAnnotation(annotation, key)) {
Object ob= mappings.get(key);
if (ob instanceof JSONObject) {
JSONObject methodMap = (JSONObject) ob;
return methodMap.getString("method");
}
protected String getRequestMethod(SingleMemberAnnotation annotation) {
ITypeBinding type = annotation.resolveTypeBinding();
if (type != null) {
switch (type.getQualifiedName()) {
case Constants.SPRING_GET_MAPPING:
return "GET";
case Constants.SPRING_POST_MAPPING:
return "POST";
case Constants.SPRING_DELETE_MAPPING:
return "DELETE";
case Constants.SPRING_PUT_MAPPING:
return "PUT";
case Constants.SPRING_PATCH_MAPPING:
return "PATCH";
}
}
return null;
}
private String getRawPath(Annotation annotation, JSONObject mappings) {
Iterator<String> keys = mappings.keys();
while (keys.hasNext()) {
String key = keys.next();
if (matchesAnnotation(annotation, key)) {
return key;
}
}
return null;
}
private boolean matchesAnnotation(Annotation annotation, String jsonKey) {
String mappingPath = null;
private boolean matchesAnnotation(Annotation annotation, RequestMapping rm) {
String[] mappingPath = null;
Set<String> methods = null;
if (annotation instanceof SingleMemberAnnotation) {
Expression valueContent = ((SingleMemberAnnotation) annotation).getValue();
SingleMemberAnnotation singleAnnotation = (SingleMemberAnnotation) annotation;
Expression valueContent = singleAnnotation.getValue();
if (valueContent instanceof StringLiteral) {
mappingPath = ((StringLiteral)valueContent).getLiteralValue();
mappingPath = new String[] { ((StringLiteral)valueContent).getLiteralValue() };
}
String method = getRequestMethod(singleAnnotation);
if (method != null) {
methods = new HashSet<>();
methods.add(method);
}
}
else if (annotation instanceof NormalAnnotation) {
List<?> values = ((NormalAnnotation) annotation).values();
for (Object value : values) {
if (value instanceof MemberValuePair) {
String name = ((MemberValuePair)value).getName().toString();
if (name != null && name.equals("value")) {
Expression valueContent = ((MemberValuePair)value).getValue();
if (valueContent instanceof StringLiteral) {
mappingPath = ((StringLiteral)valueContent).getLiteralValue();
}
MemberValuePair pair = (MemberValuePair)value;
String name = pair.getName().toString();
switch (name) {
case "value":
case "path":
mappingPath = getPaths(pair.getValue());
break;
case "method":
methods = getRequestMethod(pair.getValue());
break;
}
}
}
}
return mappingPath != null ? jsonKey.contains(mappingPath) : false;
if (mappingPath != null) {
if (Arrays.equals(mappingPath, rm.getSplitPath())) {
if (methods == null || methods.isEmpty()) {
return true;
} else {
return methods.equals(rm.getRequestMethods());
}
}
}
return false;
}
public JSONObject[] getRequestMappingsFromProcesses(SpringBootApp[] runningApps) {
List<JSONObject> result = new ArrayList<>();
private static String getExpressionValueAsString(Expression exp) {
if (exp instanceof StringLiteral) {
return ((StringLiteral)exp).getLiteralValue();
} else if (exp instanceof QualifiedName) {
return getExpressionValueAsString(((QualifiedName)exp).getName());
} else if (exp instanceof SimpleName) {
return ((SimpleName)exp).getIdentifier();
} else {
return null;
}
}
try {
for (SpringBootApp app : runningApps) {
String mappings = app.getRequestMappings();
if (mappings != null) {
JSONObject requestMappings = new JSONObject(mappings);
if (requestMappings != null) {
result.add(requestMappings);
}
}
@SuppressWarnings("unchecked")
private static String[] getPaths(Expression exp) {
if (exp instanceof ArrayInitializer) {
ArrayInitializer array = (ArrayInitializer) exp;
return ((List<Expression>)array.expressions()).stream()
.map(e -> getExpressionValueAsString(e))
.filter(Objects::nonNull)
.toArray(String[]::new);
} else {
String rm = getExpressionValueAsString(exp);
if (rm != null) {
return new String[] { rm };
}
}
catch (Exception e) {
Log.log(e);
}
return null;
}
return result.toArray(new JSONObject[result.size()]);
@SuppressWarnings("unchecked")
private static Set<String> getRequestMethod(Expression exp) {
if (exp instanceof ArrayInitializer) {
ArrayInitializer array = (ArrayInitializer) exp;
return ((List<Expression>)array.expressions()).stream()
.map(e -> getExpressionValueAsString(e))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
} else {
String rm = getExpressionValueAsString(exp);
if (rm != null) {
HashSet<String> methods = new HashSet<>();
methods.add(rm);
}
}
return null;
}
static class RequestMappingMethod {
public final SpringBootApp app;
public final String requestMappingPath;
public final JLRMethod requestMappingMethod;
public final RequestMapping requestMapping;
public RequestMappingMethod(String requestMappingPath, JLRMethod requestMappingMethod, SpringBootApp app) {
this.requestMappingPath = requestMappingPath;
this.requestMappingMethod = requestMappingMethod;
public RequestMappingMethod(RequestMapping requestMapping, SpringBootApp app) {
this.requestMapping = requestMapping;
this.app = app;
}
}

View File

@@ -10,41 +10,8 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.requestmapping;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.regex.Pattern;
import java.util.stream.Stream;
public class UrlUtil {
public static Stream<String> processOrPaths(String pathExp) {
if (pathExp.contains("||")) {
String[] paths = pathExp.split(Pattern.quote("||"));
return Stream.of(paths).map(String::trim);
} else {
return Stream.of(pathExp);
}
}
public static String extractPath(String key) {
if (key.startsWith("{[")) { //Case 2 (see above)
//An almost json string. Unfortunately not really json so we can't
//use org.json or jackson Mapper to properly parse this.
int start = 2; //right after first '['
int end = key.indexOf(']');
if (end>=2) {
return key.substring(start, end);
}
}
//Case 1, or some unanticipated stuff.
//Assume the key is the path, which is right for Case 1
// and probably more useful than null for 'unanticipated stuff'.
return key;
}
/**
* Creates http URL string based on host, port and path
* @param host
@@ -67,32 +34,4 @@ public class UrlUtil {
return null;
}
public static String[] splitPath(String path) {
if (path.contains("||")) {
List<String> result = new ArrayList<>();
String basePath = path.substring(0, path.indexOf("||")).trim();
result.add(basePath);
if (basePath.lastIndexOf('/') > 0) {
basePath = basePath.substring(0, basePath.lastIndexOf('/'));
}
String additionalPaths = path.substring(path.indexOf("||"));
StringTokenizer tokenizer = new StringTokenizer(additionalPaths, "||");
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken().trim();
if (token.length() > 0) {
result.add(basePath + "/" + token);
}
}
return result.toArray(new String[result.size()]);
}
else {
return new String[] {path};
}
}
}

View File

@@ -132,4 +132,802 @@ public class RequestMappingLiveHoverTest {
}
@Test
public void testDeleteMappingHoverHintMethod1() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/greetings],methods=[DELETE]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public void com.example.RestApi.deleteGreetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.DeleteMapping;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@DeleteMapping(\"/greetings\")\n" +
"public void deleteGreetings() {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@DeleteMapping(\"/greetings\")", "Path: [/greetings](http://cfapps.io:999/greetings)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testDeleteMappingHoverHintMethod2() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/greetings],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.deleteGreetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.DeleteMapping;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@DeleteMapping(\"/greetings\")\n" +
"public void deleteGreetings() {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertNoHover("@DeleteMapping(\"/greetings\")");
}
@Test
public void testGetMappingHoverHintMethod1() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/greetings],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.greetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import java.lang.String;\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.GetMapping;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@GetMapping(\"/greetings\")\n" +
"public String greetings() {\n" +
"return \"Greetings!\";\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@GetMapping(\"/greetings\")", "Path: [/greetings](http://cfapps.io:999/greetings)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testGetMappingHoverHintMethod2() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/greetings],methods=[DELETE]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.greetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import java.lang.String;\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.GetMapping;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@GetMapping(\"/greetings\")\n" +
"public String greetings() {\n" +
"return \"Greetings!\";\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertNoHover("@GetMapping(\"/greetings\")");
}
@Test
public void testPostMappingHoverHintMethod1() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/greetings],methods=[POST]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public void com.example.RestApi.createGreetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.PostMapping;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@PostMapping(\"/greetings\")\n" +
"public void createGreetings() {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@PostMapping(\"/greetings\")", "Path: [/greetings](http://cfapps.io:999/greetings)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testPostMappingHoverHintMethod2() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/greetings],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.createGreetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.PostMapping;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@PostMapping(\"/greetings\")\n" +
"public void createGreetings() {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertNoHover("@PostMapping(\"/greetings\")");
}
@Test
public void testPutMappingHoverHintMethod1() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/greetings],methods=[PUT]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public void com.example.RestApi.updateGreetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.PutMapping;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@PutMapping(\"/greetings\")\n" +
"public void updateGreetings() {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@PutMapping(\"/greetings\")", "Path: [/greetings](http://cfapps.io:999/greetings)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testPutMappingHoverHintMethod2() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/greetings],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.updateGreetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.PutMapping;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@PutMapping(\"/greetings\")\n" +
"public void updateGreetings() {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertNoHover("@PutMapping(\"/greetings\")");
}
@Test
public void testPatchMappingHoverHintMethod1() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/greetings],methods=[PATCH]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public void com.example.RestApi.patchGreetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.PatchMapping;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@PatchMapping(\"/greetings\")\n" +
"public void patchGreetings() {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@PatchMapping(\"/greetings\")", "Path: [/greetings](http://cfapps.io:999/greetings)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testPatchMappingHoverHintMethod2() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/greetings],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.patchGreetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.PatchMapping;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@PatchMapping(\"/greetings\")\n" +
"public void patchGreetings() {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertNoHover("@PatchMapping(\"/greetings\")");
}
@Test
public void testMultiRequestMethodMappingHoverHintMethod1() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/greetings],methods=[POST,PUT]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public void com.example.RestApi.updateGreetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value=\"/greetings\", method={POST, PUT})\n" +
"public void updateGreetings() {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@RequestMapping(value=\"/greetings\", method={POST, PUT})", "Path: [/greetings](http://cfapps.io:999/greetings)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testMultiRequestMethodMappingHoverHintMethod2() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/greetings],methods=[POST,PUT,PATCH]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.updateGreetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value=\"/greetings\", method={POST, PUT})\n" +
"public void updateGreetings() {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertNoHover("@RequestMapping(value=\"/greetings\", method={POST, PUT})");
}
@Test
public void testMultiPathMappingHoverHintMethod1() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/greetings || /hello],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.greetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value={\"/greetings\", \"/hello\"}, method=GET)\n" +
"public String greetings() {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@RequestMapping(value={\"/greetings\", \"/hello\"}, method=GET)", "Path: [/greetings](http://cfapps.io:999/greetings)\n" +
"Path: [/hello](http://cfapps.io:999/hello)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testMultiPathMappingHoverHintMethod2() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/greetings || /hello || /helloAgain],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.String com.example.RestApi.greetings()\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value={\"/greetings\", \"/hello\"}, method=GET)\n" +
"public String greetings() {\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertNoHover("@RequestMapping(value={\"/greetings\", \"/hello\"}, method=GET)");
}
@Test
public void testMethodMatchingHoverHintMethod1() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/find],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public org.springframework.http.ResponseEntity<?> com.example.RestApi.find(java.lang.String,java.util.Date,java.lang.String)\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.http.ResponseEntity;\n" +
"import java.lang.String;\n" +
"import java.util.Date;\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value=\"/find\", method=GET)\n" +
"public ResponseEntity<?> find(String p1, Date p2, String p3) {\n" +
"return null;\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "Path: [/find](http://cfapps.io:999/find)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testMethodMatchingHoverHintMethod2() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/find],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.Object com.example.RestApi.set(java.lang.String,java.util.Map<java.lang.String, java.lang.String>)\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.http.ResponseEntity;\n" +
"import java.lang.String;\n" +
"import java.util.Map;\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value=\"/find\", method=GET)\n" +
"public Object set(String p1, Map<String, String> p2) {\n" +
"return null;\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "Path: [/find](http://cfapps.io:999/find)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
@Test
public void testMethodMatchingHoverHintMethod3() throws Exception {
File directory = new File(
ProjectsHarness.class.getResource("/test-projects/test-request-mapping-live-hover/").toURI());
String docUri = "file://" +directory.getAbsolutePath() + "/src/main/java/example/RestApi.java";
// Build a mock running boot app
mockAppProvider.builder()
.isSpringBootApp(true)
.port("999")
.processId("76543")
.host("cfapps.io")
.processName("test-request-mapping-live-hover")
// Ugly, but this is real JSON copied from a real live running app. We want the
// mock app to return realistic results if possible
.getRequestMappings(
"{\"{[/find],methods=[GET]}\": {\"bean\": \"requestMappingHandlerMapping\", \"method\":\"public java.lang.Object com.example.RestApi.set(java.lang.String,java.util.Map<java.lang.String, java.util.Map<java.lang.String, java.lang.Integer>>)\"}}")
. build();
harness.intialize(directory);
Editor editor = harness.newEditor(LanguageId.JAVA,
"package com.example;\n" +
"\n" +
"import org.springframework.http.ResponseEntity;\n" +
"import java.lang.String;\n" +
"import java.lang.Integer;\n" +
"import java.util.Map;\n" +
"import org.springframework.stereotype.Controller;\n" +
"import org.springframework.web.bind.annotation.RequestMapping;\n" +
"import org.springframework.web.bind.annotation.RequestMethod.*;\n" +
"\n" +
"@Controller\n" +
"public class RestApi {\n" +
"\n" +
"@RequestMapping(value=\"/find\", method=GET)\n" +
"public Object set(String p1, Map<String, Map<String, Integer>> p2) {\n" +
"return null;\n" +
"}\n" +
"\n" +
"}",
docUri);
editor.assertHoverContains("@RequestMapping(value=\"/find\", method=GET)", "Path: [/find](http://cfapps.io:999/find)\n" +
"\n" +
"Process ID: 76543\n" +
"\n" +
"Process Name: test-request-mapping-live-hover");
}
}

View File

@@ -19,8 +19,8 @@ import java.util.Collection;
import org.mockito.Mockito;
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping;
import org.springframework.ide.vscode.commons.util.ExceptionUtil;
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness.Builder;
import com.google.common.collect.ImmutableList;
@@ -97,7 +97,8 @@ public class MockRunningAppProvider {
}
public MockAppBuilder getRequestMappings(String mappings) throws Exception {
when(app.getRequestMappings()).thenReturn(mappings);
Collection<RequestMapping> requestMappings = SpringBootApp.parseRequestMappingsJson(mappings);
when(app.getRequestMappings()).thenReturn(requestMappings);
return this;
}

View File

@@ -37,6 +37,11 @@
<groupId>org.springframework.ide.vscode</groupId>
<version>${project.version}</version>
</dependency>
<dependency>
<artifactId>commons-java</artifactId>
<groupId>org.springframework.ide.vscode</groupId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -13,7 +13,9 @@ package org.springframework.ide.vscode.commons.boot.app.cli;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -30,6 +32,8 @@ import javax.management.remote.JMXServiceURL;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMappingImpl1;
import org.springframework.ide.vscode.commons.util.Log;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -186,17 +190,29 @@ public class SpringBootApp {
return null;
}
public String getRequestMappings() throws Exception {
public static Collection<RequestMapping> parseRequestMappingsJson(String json) {
JSONObject obj = new JSONObject(json);
Iterator<String> keys = obj.keys();
List<RequestMapping> result = new ArrayList<>();
while (keys.hasNext()) {
String rawKey = keys.next();
JSONObject value = obj.getJSONObject(rawKey);
result.add(new RequestMappingImpl1(rawKey, value));
}
return result;
}
public Collection<RequestMapping> getRequestMappings() throws Exception {
Object result = getActuatorDataFromAttribute("org.springframework.boot:type=Endpoint,name=requestMappingEndpoint", "Data");
if (result != null) {
String mappings = new ObjectMapper().writeValueAsString(result);
return mappings;
return parseRequestMappingsJson(mappings);
}
result = getActuatorDataFromOperation("org.springframework.boot:type=Endpoint,name=Mappings", "mappings");
if (result != null) {
String mappings = new ObjectMapper().writeValueAsString(result);
return mappings;
return parseRequestMappingsJson(mappings);
}
return null;

View File

@@ -0,0 +1,23 @@
/*******************************************************************************
* 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.commons.boot.app.cli.requestmappings;
import java.util.Set;
public interface RequestMapping {
String getPath();
String[] getSplitPath();
String getFullyQualifiedClassName();
String getMethodName();
String[] getMethodParameters();
String getMethodString();
Set<String> getRequestMethods();
}

View File

@@ -0,0 +1,170 @@
/*******************************************************************************
* 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.commons.boot.app.cli.requestmappings;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.json.JSONObject;
import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser;
import org.springframework.ide.vscode.commons.java.parser.JLRMethodParser.JLRMethod;
import org.springframework.ide.vscode.commons.util.Log;
import com.google.common.base.Objects;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
public class RequestMappingImpl1 implements RequestMapping {
private static final Pattern REQUEST_METHODS_PATTERN = Pattern.compile(".*methods=\\[(.*)\\].*");
/*
There are two styles of entries:
1) key is a 'path' String. May contain patters like "**"
"/** /favicon.ico":{
"bean":"faviconHandlerMapping"
}
2) key is a 'almost json' String
"{[/bye],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}":{
"bean":"requestMappingHandlerMapping",
"method":"public java.lang.String demo.MyController.bye()"
}
*/
private JSONObject beanInfo;
private String pathKey;
private Supplier<JLRMethod> methodDataSupplier;
private Supplier<Set<String>> requestMethodsSupplier;
private Supplier<String> requestPathSupplier;
public RequestMappingImpl1(String pathKey, JSONObject beanInfo) {
this.pathKey = pathKey;
this.beanInfo = beanInfo;
this.requestMethodsSupplier = Suppliers.memoize(() -> parseRequestMethods());
this.requestPathSupplier = Suppliers.memoize(() -> parseRequestPath());
this.methodDataSupplier = Suppliers.memoize(() -> JLRMethodParser.parse(getMethodString()));
}
@Override
public String getPath() {
return requestPathSupplier.get();
}
@Override
public String toString() {
return "RequestMapping("+pathKey+")";
}
@Override
public String getFullyQualifiedClassName() {
JLRMethod m = getMethodData();
if (m!=null) {
return m.getFQClassName();
}
return null;
}
@Override
public String getMethodName() {
JLRMethod m = getMethodData();
if (m!=null) {
return m.getMethodName();
}
return null;
}
/**
* Returns the raw string found in the requestmapping info. This is a 'toString' value
* of java.lang.reflect.Method object.
*/
@Override
public String getMethodString() {
try {
if (beanInfo!=null) {
if (beanInfo.has("method")) {
return beanInfo.getString("method");
}
}
} catch (Exception e) {
Log.log(e);
}
return null;
}
private JLRMethod getMethodData() {
return methodDataSupplier.get();
}
@Override
public int hashCode() {
return pathKey.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
RequestMappingImpl1 other = (RequestMappingImpl1) obj;
return Objects.equal(this.pathKey, other.pathKey)
&& Objects.equal(this.getMethodString(), other.getMethodString());
}
protected Set<String> parseRequestMethods() {
Matcher matcher = REQUEST_METHODS_PATTERN.matcher(pathKey);
if (matcher.matches()) {
return Arrays.stream(matcher.group(1).split("\\s*,\\s*")).collect(Collectors.toSet());
}
return Collections.emptySet();
}
protected String parseRequestPath() {
if (pathKey.startsWith("{[")) { //Case 2 (see above)
//An almost json string. Unfortunately not really json so we can't
//use org.json or jackson Mapper to properly parse this.
int start = 2; //right after first '['
int end = pathKey.indexOf(']');
if (end>=2) {
return pathKey.substring(start, end);
}
}
//Case 1, or some unanticipated stuff.
//Assume the key is the path, which is right for Case 1
// and probably more useful than null for 'unanticipated stuff'.
return pathKey;
}
@Override
public Set<String> getRequestMethods() {
return requestMethodsSupplier.get();
}
@Override
public String[] getSplitPath() {
String paths = requestPathSupplier.get();
return Arrays.stream(paths.split("\\|\\|")).map(s -> s.trim()).toArray(String[]::new);
}
@Override
public String[] getMethodParameters() {
return getMethodData().getParameters();
}
}

View File

@@ -8,38 +8,38 @@
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.requestmapping.test;
package org.springframework.ide.vscode.commons.boot.app.cli;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.springframework.ide.vscode.boot.java.requestmapping.UrlUtil;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMappingImpl1;
/**
* @author Martin Lippert
*/
public class UrlUtilTest {
public class RequestMappingImp1Test {
@Test
public void testSplitPathWithoutDuplicate() {
String path = "/superpath";
String[] splitPath = UrlUtil.splitPath(path);
RequestMappingImpl1 rm = new RequestMappingImpl1("/superpath", null);
String[] splitPath = rm.getSplitPath();
assertEquals(1, splitPath.length);
assertEquals("/superpath", splitPath[0]);
}
@Test
public void testSplitPathSimpleCaseWithEmptyOr() {
String path = "/superpath/mypath || ";
String[] splitPath = UrlUtil.splitPath(path);
RequestMappingImpl1 rm = new RequestMappingImpl1("/superpath/mypath || ", null);
String[] splitPath = rm.getSplitPath();
assertEquals(1, splitPath.length);
assertEquals("/superpath/mypath", splitPath[0]);
}
@Test
public void testSplitPathSimpleCase() {
String path = "/superpath/mypath || mypath.json";
String[] splitPath = UrlUtil.splitPath(path);
RequestMappingImpl1 rm = new RequestMappingImpl1("/superpath/mypath || mypath.json", null);
String[] splitPath = rm.getSplitPath();
assertEquals(2, splitPath.length);
assertEquals("/superpath/mypath", splitPath[0]);
assertEquals("/superpath/mypath.json", splitPath[1]);
@@ -47,8 +47,8 @@ public class UrlUtilTest {
@Test
public void testSplitPathMultipleCases() {
String path = "/superpath/mypath || mypath.json || somethingelse.what";
String[] splitPath = UrlUtil.splitPath(path);
RequestMappingImpl1 rm = new RequestMappingImpl1("/superpath/mypath || mypath.json || somethingelse.what", null);
String[] splitPath = rm.getSplitPath();
assertEquals(3, splitPath.length);
assertEquals("/superpath/mypath", splitPath[0]);
assertEquals("/superpath/mypath.json", splitPath[1]);

View File

@@ -18,6 +18,7 @@ import static org.junit.Assert.assertTrue;
import java.io.File;
import java.net.URL;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -26,6 +27,7 @@ import org.json.JSONObject;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.ide.vscode.commons.boot.app.cli.requestmappings.RequestMapping;
import org.springframework.ide.vscode.commons.util.AsyncProcess;
import org.springframework.ide.vscode.commons.util.ExternalCommand;
import org.springframework.ide.vscode.commons.util.StringUtil;
@@ -139,8 +141,8 @@ public class SpringBootAppTest {
@Test
public void getRequestMappings() throws Exception {
ACondition.waitFor(TIMEOUT, () -> {
String result = testApp.getRequestMappings();
assertNonEmptyJsonObject(result);
Collection<RequestMapping> result = testApp.getRequestMappings();
assertTrue(result != null && !result.isEmpty());
// System.out.println("requestMappings = "+result);
});
}

View File

@@ -10,9 +10,11 @@
*******************************************************************************/
package org.springframework.ide.vscode.commons.java.parser;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
@@ -32,6 +34,8 @@ public class JLRMethodParser {
private String fqClass;
private String methodName;
private String returnType;
private String[] parameters;
//TODO: parsing arguments to handle overloading
@@ -49,6 +53,7 @@ public class JLRMethodParser {
while (modifiersEnd<pieces.length && isModifier(pieces[modifiersEnd])) {
modifiersEnd++;
}
returnType = pieces[modifiersEnd];
if (pieces.length>=modifiersEnd+2) {
methodString = pieces[modifiersEnd+1];
int methodNameEnd = methodString.indexOf('(');
@@ -63,6 +68,55 @@ public class JLRMethodParser {
}
}
}
int parametersStart = rawString.indexOf('(');
int parametersEnd = rawString.indexOf(')');
if (parametersStart < parametersEnd) {
String parametersString = rawString.substring(parametersStart + 1, parametersEnd);
this.parameters = parseParameters(parametersString);
} else {
this.parameters = new String[0];
}
}
private String[] parseParameters(String parameterString) {
List<String> parameters = new ArrayList<>();
int openTemplateParameters = 0;
StringBuilder currentParameter = new StringBuilder();
for (int i = 0; i < parameterString.length(); i++) {
char ch = parameterString.charAt(i);
switch (ch) {
case '.':
currentParameter.append(ch);
break;
case '<':
openTemplateParameters++;
currentParameter.append(ch);
break;
case '>':
openTemplateParameters--;
currentParameter.append(ch);
break;
case ',' :
if (openTemplateParameters == 0) {
if (currentParameter.length() != 0) {
parameters.add(currentParameter.toString());
currentParameter = new StringBuilder();
}
} else {
currentParameter.append(ch);
}
break;
default:
if (Character.isJavaIdentifierPart(ch)) {
currentParameter.append(ch);
}
}
}
if (currentParameter.length() > 0) {
parameters.add(currentParameter.toString());
}
return parameters.toArray(new String[parameters.size()]);
}
@Override
@@ -77,6 +131,14 @@ public class JLRMethodParser {
public String getMethodName() {
return methodName;
}
public String getReturnType() {
return returnType;
}
public String[] getParameters() {
return parameters;
}
}