Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Development: Improve endpoint analysis #9236

Merged
merged 11 commits into from
Aug 31, 2024
Merged
12 changes: 9 additions & 3 deletions .github/workflows/analysis-of-endpoint-connections.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ name: Analysis of Endpoint Connections

on:
workflow_dispatch:
push:
pull_request:
types:
- opened
- synchronize
paths:
- 'src/main/java/**'
- 'src/main/webapp/**'

# Keep in sync with build.yml and test.yml and codeql-analysis.yml
env:
Expand All @@ -20,7 +26,7 @@ jobs:
with:
fetch-depth: 0

- name: Set up JDK 21
- name: Set up JDK
uses: actions/setup-java@v4
with:
java-version: '${{ env.java }}'
Expand Down Expand Up @@ -59,7 +65,7 @@ jobs:
with:
fetch-depth: 0

- name: Set up JDK 21
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: 'temurin'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@

public class EndpointAnalyzer {

private static String EndpointAnalysisResultPath = "endpointAnalysisResult.json";
private static String ENDPOINT_ANALYSIS_RESULT_PATH = "endpointAnalysisResult.json";

private static final Logger logger = LoggerFactory.getLogger(EndpointAnalyzer.class);
private static final Logger log = LoggerFactory.getLogger(EndpointAnalyzer.class);

public static void main(String[] args) {
analyzeEndpoints();
Expand Down Expand Up @@ -60,10 +60,13 @@ private static void analyzeEndpoints() {
for (EndpointInformation endpoint : endpointClass.endpoints()) {

String endpointURI = endpoint.buildComparableEndpointUri();
List<RestCallInformation> matchingRestCalls = restCallMap.getOrDefault(endpointURI, new ArrayList<>());
List<RestCallInformation> restCallsWithMatchingURI = restCallMap.getOrDefault(endpointURI, new ArrayList<>());

// Check for wildcard endpoints if no exact match is found
checkForWildcardEndpoints(endpoint, matchingRestCalls, endpointURI, restCallMap);
checkForWildcardEndpoints(endpoint, restCallsWithMatchingURI, endpointURI, restCallMap);

List<RestCallInformation> matchingRestCalls = restCallsWithMatchingURI.stream()
.filter(restCall -> restCall.method().toLowerCase().equals(endpoint.getHttpMethod().toLowerCase())).toList();
Jan-Thurner marked this conversation as resolved.
Show resolved Hide resolved

if (matchingRestCalls.isEmpty()) {
unusedEndpoints.add(endpoint);
Expand All @@ -75,10 +78,10 @@ private static void analyzeEndpoints() {
}

EndpointAnalysis endpointAnalysis = new EndpointAnalysis(endpointsAndMatchingRestCalls, unusedEndpoints);
mapper.writeValue(new File(EndpointAnalysisResultPath), endpointAnalysis);
mapper.writeValue(new File(ENDPOINT_ANALYSIS_RESULT_PATH), endpointAnalysis);
}
catch (IOException e) {
logger.error("Failed to analyze endpoints", e);
log.error("Failed to analyze endpoints", e);
}
}

Expand Down Expand Up @@ -119,26 +122,26 @@ private static void printEndpointAnalysisResult() {
ObjectMapper mapper = new ObjectMapper();
EndpointAnalysis endpointsAndMatchingRestCalls = null;
try {
endpointsAndMatchingRestCalls = mapper.readValue(new File(EndpointAnalysisResultPath), new TypeReference<EndpointAnalysis>() {
endpointsAndMatchingRestCalls = mapper.readValue(new File(ENDPOINT_ANALYSIS_RESULT_PATH), new TypeReference<EndpointAnalysis>() {
});
}
catch (IOException e) {
logger.error("Failed to deserialize endpoint analysis result", e);
log.error("Failed to deserialize endpoint analysis result", e);
return;
}

endpointsAndMatchingRestCalls.unusedEndpoints().stream().forEach(endpoint -> {
logger.info("=============================================");
logger.info("Endpoint URI: {}", endpoint.buildCompleteEndpointURI());
logger.info("HTTP method: {}", endpoint.httpMethodAnnotation());
logger.info("File path: {}", endpoint.className());
logger.info("Line: {}", endpoint.line());
logger.info("=============================================");
logger.info("No matching REST call found for endpoint: {}", endpoint.buildCompleteEndpointURI());
logger.info("---------------------------------------------");
logger.info("");
log.info("=============================================");
log.info("Endpoint URI: {}", endpoint.buildCompleteEndpointURI());
log.info("HTTP method: {}", endpoint.httpMethodAnnotation());
log.info("File path: {}", endpoint.className());
log.info("Line: {}", endpoint.line());
log.info("=============================================");
log.info("No matching REST call found for endpoint: {}", endpoint.buildCompleteEndpointURI());
log.info("---------------------------------------------");
log.info("");
});

logger.info("Number of endpoints without matching REST calls: {}", endpointsAndMatchingRestCalls.unusedEndpoints().size());
log.info("Number of endpoints without matching REST calls: {}", endpointsAndMatchingRestCalls.unusedEndpoints().size());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import com.fasterxml.jackson.annotation.JsonIgnore;

public record EndpointInformation(String requestMapping, String endpoint, String httpMethodAnnotation, String URI, String className, int line, List<String> otherAnnotations) {
public record EndpointInformation(String requestMapping, String endpoint, String httpMethodAnnotation, String uri, String className, int line, List<String> otherAnnotations) {

public String buildCompleteEndpointURI() {
StringBuilder result = new StringBuilder();
Expand All @@ -13,7 +13,7 @@ public String buildCompleteEndpointURI() {
result.append(this.requestMapping.replace("\"", ""));
}
// Remove quotes from the URI as they are used to define the String in the source code but are not part of the URI
result.append(this.URI.replace("\"", ""));
result.append(this.uri.replace("\"", ""));
return result.toString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class EndpointParser {

static final String REST_CALL_PARSING_RESULT_PATH = "restCalls.json";

private static final Logger logger = LoggerFactory.getLogger(EndpointParser.class);
private static final Logger log = LoggerFactory.getLogger(EndpointParser.class);

public static void main(String[] args) {
final Path absoluteDirectoryPath = Path.of("../../src/main/java").toAbsolutePath().normalize();
Expand All @@ -48,7 +48,7 @@ public static void main(String[] args) {
filesToParse = paths.filter(Files::isRegularFile).filter(path -> path.toString().endsWith(".java")).map(Path::toString).toArray(String[]::new);
}
catch (IOException e) {
logger.error("Error reading files from directory: {}", absoluteDirectoryPath, e);
log.error("Error reading files from directory: {}", absoluteDirectoryPath, e);
}

parseServerEndpoints(filesToParse);
Expand Down Expand Up @@ -190,9 +190,9 @@ else if (annotation instanceof NormalAnnotationExpr normalAnnotationExpr) {
*/
private static void printFilesFailedToParse(List<String> filesFailedToParse) {
if (!filesFailedToParse.isEmpty()) {
logger.warn("Files failed to parse:", filesFailedToParse);
log.warn("Files failed to parse:", filesFailedToParse);
for (String file : filesFailedToParse) {
logger.warn(file);
log.warn(file);
}
}
}
Expand All @@ -211,7 +211,7 @@ private static void writeEndpointsToFile(List<EndpointClassInformation> endpoint
new ObjectMapper().writeValue(new File(ENDPOINT_PARSING_RESULT_PATH), endpointClasses);
}
catch (IOException e) {
logger.error("Failed to write endpoint information to file", e);
log.error("Failed to write endpoint information to file", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class RestCallAnalyzer {

private static final String REST_CALL_ANALYSIS_RESULT_PATH = "restCallAnalysisResult.json";

private static final Logger logger = LoggerFactory.getLogger(RestCallAnalyzer.class);
private static final Logger log = LoggerFactory.getLogger(RestCallAnalyzer.class);

public static void main(String[] args) {
analyzeRestCalls();
Expand Down Expand Up @@ -58,16 +58,19 @@ private static void analyzeRestCalls() {
for (RestCallFileInformation restCallFile : restCalls) {
for (RestCallInformation restCall : restCallFile.restCalls()) {
String restCallURI = restCall.buildComparableRestCallUri();
List<EndpointInformation> matchingEndpoints = endpointMap.getOrDefault(restCallURI, new ArrayList<>());
List<EndpointInformation> endpointsWithMatchingUri = endpointMap.getOrDefault(restCallURI, new ArrayList<>());

checkForWildcardMatches(restCall, matchingEndpoints, restCallURI, endpointMap);
checkForWildcardMatches(restCall, endpointsWithMatchingUri, restCallURI, endpointMap);

if (matchingEndpoints.isEmpty()) {
List<EndpointInformation> endpointsWithMatchingHttpMethod = endpointsWithMatchingUri.stream()
.filter(endpoint -> endpoint.getHttpMethod().toLowerCase().equals(restCall.method().toLowerCase())).toList();

if (endpointsWithMatchingHttpMethod.isEmpty()) {
restCallsWithoutMatchingEndpoint.add(restCall);
}
else {
for (EndpointInformation endpoint : matchingEndpoints) {
restCallsWithMatchingEndpoint.add(new RestCallWithMatchingEndpoint(endpoint, restCall, restCall.fileName()));
for (EndpointInformation endpoint : endpointsWithMatchingHttpMethod) {
restCallsWithMatchingEndpoint.add(new RestCallWithMatchingEndpoint(endpoint, restCall, restCall.filePath()));
}
}
}
Expand All @@ -77,7 +80,7 @@ private static void analyzeRestCalls() {
mapper.writeValue(new File(REST_CALL_ANALYSIS_RESULT_PATH), restCallAnalysis);
}
catch (IOException e) {
logger.error("Failed to analyze REST calls", e);
log.error("Failed to analyze REST calls", e);
}
}

Expand Down Expand Up @@ -124,21 +127,22 @@ private static void printRestCallAnalysisResult() {
});
}
catch (IOException e) {
logger.error("Failed to deserialize rest call analysis results", e);
log.error("Failed to deserialize rest call analysis results", e);
return;
}

restCallsAndMatchingEndpoints.restCallsWithoutMatchingEndpoints().stream().forEach(endpoint -> {
logger.info("=============================================");
logger.info("REST call URI: {}", endpoint.buildCompleteRestCallURI());
logger.info("HTTP method: {}", endpoint.method());
logger.info("File path: {}", endpoint.fileName());
logger.info("Line: {}", endpoint.line());
logger.info("=============================================");
logger.info("No matching endpoint found for REST call: {}", endpoint.buildCompleteRestCallURI());
logger.info("---------------------------------------------");
logger.info("");
log.info("=============================================");
log.info("REST call URI: {}", endpoint.buildCompleteRestCallURI());
log.info("HTTP method: {}", endpoint.method());
log.info("File path: {}", endpoint.filePath());
log.info("Line: {}", endpoint.line());
log.info("=============================================");
log.info("No matching endpoint found for REST call: {}", endpoint.buildCompleteRestCallURI());
log.info("---------------------------------------------");
log.info("");
});

logger.info("Number of REST calls without matching endpoints: {}", restCallsAndMatchingEndpoints.restCallsWithoutMatchingEndpoints().size());
log.info("Number of REST calls without matching endpoints: {}", restCallsAndMatchingEndpoints.restCallsWithoutMatchingEndpoints().size());
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package de.tum.cit.endpointanalysis;

public record RestCallFileInformation(String fileName, RestCallInformation[] restCalls) {
import java.util.List;

public record RestCallFileInformation(String filePath, List<RestCallInformation> restCalls) {
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package de.tum.cit.endpointanalysis;

public record RestCallInformation(String method, String url, int line, String fileName) {
public record RestCallInformation(String method, String url, String filePath, int line) {

public String buildCompleteRestCallURI() {
return this.url.replace("`", "");
Expand All @@ -12,6 +12,9 @@ public String buildComparableRestCallUri() {

// Remove query parameters
result = result.split("\\?")[0];
if (result.startsWith("/")) {
result = result.substring(1);
}
Jan-Thurner marked this conversation as resolved.
Show resolved Hide resolved
Jan-Thurner marked this conversation as resolved.
Show resolved Hide resolved

return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ interface RestCall {
method: string;
url: string;
line: number;
fileName: string;
filePath: string;
}

enum ParsingResultType {
Expand Down Expand Up @@ -38,17 +38,17 @@ class ParsingResult {
}

export class Postprocessor {
static filesWithRestCalls: { fileName: string, restCalls: RestCall[] }[] = [];
static filesWithRestCalls: { filePath: string, restCalls: RestCall[] }[] = [];
private readonly restCalls: RestCall[] = [];
private readonly fileName: string;
private readonly filePath: string;
private readonly ast: TSESTree.Program;

/**
* @param fileName - The name of the file being processed.
* @param filePath - The name of the file being processed.
Jan-Thurner marked this conversation as resolved.
Show resolved Hide resolved
* @param ast - The abstract syntax tree (AST) of the processed file.
*/
constructor(fileName: string, ast: TSESTree.Program) {
this.fileName = fileName;
constructor(filePath: string, ast: TSESTree.Program) {
this.filePath = filePath;
this.ast = ast;
}

Expand All @@ -61,7 +61,7 @@ export class Postprocessor {
}
});
if (this.restCalls.length > 0) {
Postprocessor.filesWithRestCalls.push( {fileName: this.fileName, restCalls: this.restCalls} );
Postprocessor.filesWithRestCalls.push( {filePath: this.filePath, restCalls: this.restCalls} );
Jan-Thurner marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down Expand Up @@ -108,10 +108,10 @@ export class Postprocessor {
urlEvaluationResult = this.evaluateUrl(node.arguments[0], methodDefinition, node, classBody);
}

const fileName = this.fileName;
const filePath = this.filePath;
if (urlEvaluationResult.resultType === ParsingResultType.EVALUATE_URL_SUCCESS) {
for (let url of urlEvaluationResult.result) {
Jan-Thurner marked this conversation as resolved.
Show resolved Hide resolved
this.restCalls.push({ method, url, line, fileName });
this.restCalls.push({ method, url, line, filePath: filePath });
Jan-Thurner marked this conversation as resolved.
Show resolved Hide resolved
Jan-Thurner marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
Expand Down Expand Up @@ -619,7 +619,7 @@ export class Postprocessor {
for (let i = 0; i < superConstructorCallArguments.arguments.length; i++) {
let constructorArgument = constructorArguments[i];
if (superConstructorCallArguments.arguments[i] !== '' && constructorArgument.type === 'TSParameterProperty'
&& constructorArgument.parameter.type === 'Identifier' && constructorArgument.parameter.name === memberExprKey) {
&& constructorArgument.parameter.type === 'Identifier' && constructorArgument.parameter.name === memberExprKey) {
Jan-Thurner marked this conversation as resolved.
Show resolved Hide resolved
memberExpressionResult.push(superConstructorCallArguments.arguments[i]);
resultType = ParsingResultType.EVALUATE_MEMBER_EXPRESSION_SUCCESS;
}
Expand Down
Loading