Skip to content

Commit abe4193

Browse files
committed
Feature/GRPC Server vanilla - endpoints discovery #246
1 parent de97875 commit abe4193

File tree

3 files changed

+173
-0
lines changed

3 files changed

+173
-0
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
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.PsiClass;
7+
import com.intellij.psi.PsiField;
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.ClassInheritorsSearch;
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.Arrays;
20+
import java.util.Collection;
21+
import java.util.Objects;
22+
23+
public class GrpcFramework {
24+
private static final Logger LOGGER = Logger.getInstance(GrpcFramework.class);
25+
26+
public static final String BINDABLE_SERVICE_ANNOTATION_STR = "io.grpc.BindableService";
27+
public static final String DIGMA_UNKNOWN_SERVICE_NAME = "Digma.Unknown.Grpc.Service";
28+
29+
private final Project project;
30+
31+
// late init
32+
private boolean lateInitAlready = false;
33+
private PsiClass bindableServiceAnnotationClass;
34+
35+
public GrpcFramework(Project project) {
36+
this.project = project;
37+
}
38+
39+
private void lateInit() {
40+
if (lateInitAlready) return;
41+
42+
JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
43+
bindableServiceAnnotationClass = psiFacade.findClass(BINDABLE_SERVICE_ANNOTATION_STR, GlobalSearchScope.allScope(project));
44+
Log.log(LOGGER::info, "GRPC init. isGrpcServerRelevant='{}'", isGrpcServerRelevant());
45+
46+
lateInitAlready = true;
47+
}
48+
49+
private boolean isGrpcServerRelevant() {
50+
return bindableServiceAnnotationClass != null;
51+
}
52+
53+
public void endpointDiscovery(@NotNull PsiFile psiFile, @NotNull DocumentInfo documentInfo) {
54+
lateInit();
55+
if (!isGrpcServerRelevant()) {
56+
return;
57+
}
58+
59+
Query<PsiClass> grpcServerClassesInFile = ClassInheritorsSearch.search(bindableServiceAnnotationClass, GlobalSearchScope.fileScope(psiFile), true);
60+
61+
for (PsiClass currGrpcServerClass : grpcServerClassesInFile) {
62+
if (JavaPsiUtils.isBaseClass(currGrpcServerClass)) {
63+
// if has no super class then it is the generated GRPC server class, we do not want it
64+
Log.log(LOGGER::debug, "endpointDiscovery, skip bindableService GrpcServerClass fqn='{}' since it is the generated GRPC base service", currGrpcServerClass.getQualifiedName());
65+
continue;
66+
}
67+
Log.log(LOGGER::debug, "endpointDiscovery, bingo - its a GRPC server class, its fqn='{}'", currGrpcServerClass.getQualifiedName());
68+
addEndpointMethods(currGrpcServerClass, documentInfo);
69+
}
70+
}
71+
72+
protected void addEndpointMethods(@NotNull PsiClass grpcServerClass, @NotNull DocumentInfo documentInfo) {
73+
String grpcServiceName = evaluateServiceName(grpcServerClass);
74+
Log.log(LOGGER::debug, "addEndpointMethods for grpcServerClass fqn='{}' with evaluated serviceName='{}'", grpcServerClass.getQualifiedName(), grpcServiceName);
75+
76+
Collection<PsiMethod> psiMethods = Arrays.asList(grpcServerClass.getMethods());
77+
for (PsiMethod currPsiMethod : psiMethods) {
78+
String methodCodeObjectId = JavaLanguageUtils.createJavaMethodCodeObjectId(currPsiMethod);
79+
80+
String endpointId = createEndpointId(grpcServiceName, currPsiMethod);
81+
//PsiParameterList parameterList = currPsiMethod.getParameterList(); //TODO: maybe search for parameters of type io.grpc.stub.StreamObserver
82+
EndpointInfo endpointInfo = new EndpointInfo(endpointId, methodCodeObjectId, documentInfo.getFileUri());
83+
84+
MethodInfo methodInfo = documentInfo.getMethods().get(endpointInfo.getContainingMethodId());
85+
//this method must exist in the document info
86+
Objects.requireNonNull(methodInfo, "method info " + endpointInfo.getContainingMethodId() + " must exist in DocumentInfo for " + documentInfo.getFileUri());
87+
methodInfo.addEndpoint(endpointInfo);
88+
}
89+
}
90+
91+
@NotNull
92+
protected String evaluateServiceName(@NotNull PsiClass grpcServerClass) {
93+
PsiClass generatedGrpcBasePsiClass = JavaPsiUtils.climbUpToBaseClass(grpcServerClass); // for example GreeterGrpc.GreeterImplBase
94+
PsiClass generatedGrpcContainingPsiClass = generatedGrpcBasePsiClass.getContainingClass(); // for example GreeterGrpc
95+
if (generatedGrpcContainingPsiClass == null) {
96+
Log.log(LOGGER::warn, "evaluateServiceName:#PotentialBug: could not find containing (generated) class for generated GRPC ImplBase Class fqn='{}'", generatedGrpcBasePsiClass.getQualifiedName());
97+
return DIGMA_UNKNOWN_SERVICE_NAME; // very unlikely
98+
}
99+
100+
// searching for generated field named SERVICE_NAME, for example :
101+
// public static final String SERVICE_NAME = "helloworld.Greeter"
102+
PsiField serviceNamePsiField = generatedGrpcContainingPsiClass.findFieldByName("SERVICE_NAME", false);
103+
if (serviceNamePsiField == null) {
104+
Log.log(LOGGER::warn, "evaluateServiceName:#PotentialBug: could not find field 'SERVICE_NAME' in containing (generated) class of GRPC fqn='{}'", generatedGrpcContainingPsiClass.getQualifiedName());
105+
return DIGMA_UNKNOWN_SERVICE_NAME; // very unlikely
106+
}
107+
108+
Object serviceNameValueAsObj = serviceNamePsiField.computeConstantValue();
109+
if (serviceNameValueAsObj == null) {
110+
Log.log(LOGGER::warn, "evaluateServiceName:#PotentialBug: could not evaluate value of field 'SERVICE_NAME' in containing (generated) class of GRPC fqn='{}', with field={}", generatedGrpcContainingPsiClass.getQualifiedName(), serviceNamePsiField);
111+
return DIGMA_UNKNOWN_SERVICE_NAME; // very unlikely
112+
}
113+
114+
return serviceNameValueAsObj.toString();
115+
}
116+
117+
@NotNull
118+
protected String createEndpointId(@NotNull String grpcServiceName, @NotNull PsiMethod psiMethod) {
119+
//value for example: "epRPC:helloworld.Greeter/SayHello"
120+
return "" +
121+
// digma part
122+
"epRPC:" +
123+
// GRPC part
124+
grpcServiceName + "/" + capitalizeFirstLetter(psiMethod.getName());
125+
}
126+
127+
@NotNull
128+
private static String capitalizeFirstLetter(@NotNull String value) {
129+
char firstCharUcase = Character.toUpperCase(value.charAt(0));
130+
return firstCharUcase + value.substring(1);
131+
}
132+
133+
}

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
@@ -70,13 +70,15 @@ public class JavaLanguageService implements LanguageService {
7070
private final CaretContextService caretContextService;
7171
private final MicronautFramework micronautFramework;
7272
private final JaxrsFramework jaxrsFramework;
73+
private final GrpcFramework grpcFramework;
7374

7475
public JavaLanguageService(Project project) {
7576
this.project = project;
7677
documentInfoService = project.getService(DocumentInfoService.class);
7778
caretContextService = project.getService(CaretContextService.class);
7879
this.micronautFramework = new MicronautFramework(project);
7980
this.jaxrsFramework = new JaxrsFramework(project);
81+
this.grpcFramework = new GrpcFramework(project);
8082
}
8183

8284

@@ -407,6 +409,7 @@ private void endpointDiscovery(PsiFile psiFile, DocumentInfo documentInfo) {
407409
Log.log(LOGGER::debug, "Building endpoints for file {}", psiFile);
408410
micronautFramework.endpointDiscovery(psiFile, documentInfo);
409411
jaxrsFramework.endpointDiscovery(psiFile, documentInfo);
412+
grpcFramework.endpointDiscovery(psiFile, documentInfo);
410413
}
411414

412415

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.digma.intellij.plugin.idea.psi.java
2+
3+
import com.intellij.psi.PsiClass
4+
import org.jetbrains.annotations.NotNull
5+
6+
class JavaPsiUtils {
7+
8+
companion object {
9+
private const val OBJECT_CLASS_FQN = "java.lang.Object"
10+
11+
@JvmStatic
12+
fun isBaseClass(psiClass: PsiClass): Boolean {
13+
val superClass = psiClass.superClass
14+
return (superClass == null || superClass.qualifiedName.equals(OBJECT_CLASS_FQN))
15+
}
16+
17+
/**
18+
* Climbs up from this class to its super classes and searches for the first class that is after the java.lang.Object.
19+
*/
20+
@JvmStatic
21+
@NotNull
22+
fun climbUpToBaseClass(psiClass: PsiClass): PsiClass {
23+
var prevCLass: PsiClass = psiClass
24+
var currentClass: PsiClass? = psiClass
25+
while (currentClass != null && !isBaseClass(currentClass)) {
26+
prevCLass = currentClass
27+
currentClass = currentClass.superClass // recursion
28+
}
29+
30+
if (currentClass != null) {
31+
return currentClass
32+
} else {
33+
return prevCLass
34+
}
35+
}
36+
}
37+
}

0 commit comments

Comments
 (0)