Skip to content

Commit feb4b11

Browse files
committed
Feature/JAX-RS server endpoint discovery (#238)
1 parent b48ac94 commit feb4b11

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

java/src/main/java/org/digma/intellij/plugin/idea/psi/java/JavaLanguageService.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,14 @@ public class JavaLanguageService implements LanguageService {
6969

7070
private final CaretContextService caretContextService;
7171
private final MicronautFramework micronautFramework;
72+
private final JaxrsFramework jaxrsFramework;
7273

7374
public JavaLanguageService(Project project) {
7475
this.project = project;
7576
documentInfoService = project.getService(DocumentInfoService.class);
7677
caretContextService = project.getService(CaretContextService.class);
7778
this.micronautFramework = new MicronautFramework(project);
79+
this.jaxrsFramework = new JaxrsFramework(project);
7880
}
7981

8082

@@ -404,6 +406,7 @@ private void spanDiscovery(PsiFile psiFile, DocumentInfo documentInfo) {
404406
private void endpointDiscovery(PsiFile psiFile, DocumentInfo documentInfo) {
405407
Log.log(LOGGER::debug, "Building endpoints for file {}", psiFile);
406408
micronautFramework.endpointDiscovery(psiFile, documentInfo);
409+
jaxrsFramework.endpointDiscovery(psiFile, documentInfo);
407410
}
408411

409412

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package org.digma.intellij.plugin.idea.psi.java;
2+
3+
import com.intellij.openapi.diagnostic.Logger;
4+
import com.intellij.openapi.project.Project;
5+
import com.intellij.psi.JavaPsiFacade;
6+
import com.intellij.psi.PsiAnnotation;
7+
import com.intellij.psi.PsiClass;
8+
import com.intellij.psi.PsiFile;
9+
import com.intellij.psi.PsiMethod;
10+
import com.intellij.psi.search.GlobalSearchScope;
11+
import com.intellij.psi.search.searches.AnnotatedElementsSearch;
12+
import com.intellij.util.Query;
13+
import org.digma.intellij.plugin.log.Log;
14+
import org.digma.intellij.plugin.model.discovery.DocumentInfo;
15+
import org.digma.intellij.plugin.model.discovery.EndpointInfo;
16+
import org.digma.intellij.plugin.model.discovery.MethodInfo;
17+
import org.jetbrains.annotations.NotNull;
18+
19+
import java.util.List;
20+
import java.util.Objects;
21+
import java.util.stream.Collectors;
22+
23+
public class JaxrsFramework {
24+
25+
private static final Logger LOGGER = Logger.getInstance(JaxrsFramework.class);
26+
private static final String JAX_RS_PATH_ANNOTATION_STR = "javax.ws.rs.Path";
27+
private static final String HTTP_DELETE_ANNOTATION_STR = "javax.ws.rs.DELETE";
28+
private static final String HTTP_GET_ANNOTATION_STR = "javax.ws.rs.GET";
29+
private static final String HTTP_HEAD_ANNOTATION_STR = "javax.ws.rs.HEAD";
30+
private static final String HTTP_OPTIONS_ANNOTATION_STR = "javax.ws.rs.OPTIONS";
31+
private static final String HTTP_PATCH_ANNOTATION_STR = "javax.ws.rs.PATCH";
32+
private static final String HTTP_POST_ANNOTATION_STR = "javax.ws.rs.POST";
33+
private static final String HTTP_PUT_ANNOTATION_STR = "javax.ws.rs.PUT";
34+
private static final List<String> HTTP_METHODS_ANNOTATION_STR_LIST = List.of(
35+
HTTP_DELETE_ANNOTATION_STR, HTTP_GET_ANNOTATION_STR, HTTP_HEAD_ANNOTATION_STR, HTTP_OPTIONS_ANNOTATION_STR,
36+
HTTP_PATCH_ANNOTATION_STR, HTTP_POST_ANNOTATION_STR, HTTP_PUT_ANNOTATION_STR);
37+
38+
private final Project project;
39+
40+
// late init
41+
private boolean lateInitAlready = false;
42+
private PsiClass jaxrsPathAnnotationClass;
43+
private List<JavaAnnotation> httpMethodsAnnotations;
44+
45+
public JaxrsFramework(Project project) {
46+
this.project = project;
47+
}
48+
49+
private void lateInit() {
50+
if (lateInitAlready) return;
51+
52+
JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
53+
jaxrsPathAnnotationClass = psiFacade.findClass(JAX_RS_PATH_ANNOTATION_STR, GlobalSearchScope.allScope(project));
54+
initHttpMethodAnnotations(psiFacade);
55+
56+
lateInitAlready = true;
57+
}
58+
59+
private void initHttpMethodAnnotations(JavaPsiFacade psiFacade) {
60+
httpMethodsAnnotations = HTTP_METHODS_ANNOTATION_STR_LIST.stream()
61+
.map(currFqn -> {
62+
PsiClass psiClass = psiFacade.findClass(currFqn, GlobalSearchScope.allScope(project));
63+
if (psiClass == null) return null;
64+
return new JavaAnnotation(currFqn, psiClass);
65+
})
66+
.filter(Objects::nonNull)
67+
.collect(Collectors.toUnmodifiableList());
68+
}
69+
70+
private boolean isJaxRsHttpRelevant() {
71+
return jaxrsPathAnnotationClass != null;
72+
}
73+
74+
public void endpointDiscovery(@NotNull PsiFile psiFile, @NotNull DocumentInfo documentInfo) {
75+
lateInit();
76+
if (!isJaxRsHttpRelevant()) {
77+
return;
78+
}
79+
80+
httpMethodsAnnotations.forEach(currAnnotation -> {
81+
Query<PsiMethod> psiMethodsOfHttpMethodInFile = AnnotatedElementsSearch.searchPsiMethods(currAnnotation.getPsiClass(), GlobalSearchScope.fileScope(psiFile));
82+
83+
for (PsiMethod currPsiMethod : psiMethodsOfHttpMethodInFile) {
84+
PsiClass controllerClass = currPsiMethod.getContainingClass();
85+
if (controllerClass == null) {
86+
continue; // very unlikely
87+
}
88+
PsiAnnotation controllerAnnotation = controllerClass.getAnnotation(JAX_RS_PATH_ANNOTATION_STR);
89+
if (controllerAnnotation == null) {
90+
continue; // skip this method, since its class is not a controller
91+
}
92+
String endpointUriPrefix = JavaLanguageUtils.getPsiAnnotationAttributeValue(controllerAnnotation, "value");
93+
94+
String endpointUriSuffix = "";
95+
PsiAnnotation methodPathAnnotation = currPsiMethod.getAnnotation(JAX_RS_PATH_ANNOTATION_STR); // optional annotation on method
96+
if (methodPathAnnotation != null) {
97+
endpointUriSuffix = JavaLanguageUtils.getPsiAnnotationAttributeValue(methodPathAnnotation, "value");
98+
}
99+
100+
String endpointFullUri = JavaUtils.combineUri(endpointUriPrefix, endpointUriSuffix);
101+
102+
String httpEndpointCodeObjectId = createHttpEndpointCodeObjectId(currAnnotation, endpointFullUri);
103+
104+
EndpointInfo endpointInfo = new EndpointInfo(httpEndpointCodeObjectId, JavaLanguageUtils.createJavaMethodCodeObjectId(currPsiMethod), documentInfo.getFileUri());
105+
Log.log(LOGGER::debug, "Found endpoint info '{}' for method '{}'", endpointInfo.getId(), endpointInfo.getContainingMethodId());
106+
107+
MethodInfo methodInfo = documentInfo.getMethods().get(endpointInfo.getContainingMethodId());
108+
//this method must exist in the document info
109+
Objects.requireNonNull(methodInfo, "method info " + endpointInfo.getContainingMethodId() + " must exist in DocumentInfo for " + documentInfo.getFileUri());
110+
methodInfo.addEndpoint(endpointInfo);
111+
}
112+
});
113+
}
114+
115+
@NotNull
116+
protected static String createHttpEndpointCodeObjectId(JavaAnnotation httpMethodAnnotation, String endpointFullUri) {
117+
String httpMethodUcase = getHttpMethod(httpMethodAnnotation).toUpperCase();
118+
119+
// value for example : 'epHTTP:HTTP GET /books/get'
120+
return "" +
121+
// digma part
122+
"epHTTP:" + "HTTP " + httpMethodUcase + " " +
123+
// JaxRs part
124+
endpointFullUri
125+
;
126+
}
127+
128+
@NotNull
129+
private static String getHttpMethod(JavaAnnotation javaAnnotation) {
130+
String fqn = javaAnnotation.getClassNameFqn();
131+
int lastIndexOfDot = fqn.lastIndexOf('.');
132+
if (lastIndexOfDot >= 0) {
133+
return fqn.substring(lastIndexOfDot + 1);
134+
} else {
135+
return fqn;
136+
}
137+
}
138+
}

0 commit comments

Comments
 (0)