diff --git a/.gitignore b/.gitignore index 155021a4..c52f1c99 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ commands.txt /clone/ /.vscode /logs +/node_modules/ diff --git a/config.json b/config.json index 2d86cccc..ba90e41e 100644 --- a/config.json +++ b/config.json @@ -1,5 +1,5 @@ { - "systemName": "Train-ticket", - "repositoryURL": "https://github.com/FudanSELab/train-ticket.git", - "baseBranch": "main" + "systemName": "sample-spring-microservices-new", + "repositoryURL": "https://github.com/piomin/sample-spring-microservices-new.git", + "baseBranch": "master" } \ No newline at end of file diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml new file mode 100644 index 00000000..d774d3ed --- /dev/null +++ b/dependency-reduced-pom.xml @@ -0,0 +1,161 @@ + + + 4.0.0 + edu.university.ecs.lab + cimet + 1.0-SNAPSHOT + + + + org.codehaus.mojo + exec-maven-plugin + 3.3.0 + + ${mainClass} + + + + maven-javadoc-plugin + 3.4.1 + + private + ./docs + ./docs + + + common + edu.university.ecs.lab.common.* + + + delta + edu.university.ecs.lab.delta.* + + + intermediate + edu.university.ecs.lab.intermediate.* + + + metrics + edu.university.ecs.lab.metrics.* + + + + + + maven-jar-plugin + 3.2.0 + + + + true + ${mainClass} + + + + + + maven-shade-plugin + 3.2.4 + + + package + + shade + + + + + ${mainClass} + + + + + + + + + + + run-ir + + exec:java + + + edu.university.ecs.lab.intermediate.create.IRExtractionRunner + + + + run-delta + + exec:java + + + edu.university.ecs.lab.delta.DeltaExtractionRunner + + + + run-merge + + exec:java + + + edu.university.ecs.lab.intermediate.merge.IRMergeRunner + + + + run-history + + exec:java + + + edu.university.ecs.lab.detection.ExcelOutputRunner + + + + + + org.projectlombok + lombok + 1.18.30 + provided + + + junit + junit + 4.13.2 + test + + + hamcrest-core + org.hamcrest + + + + + org.junit.jupiter + junit-jupiter + RELEASE + test + + + junit-jupiter-api + org.junit.jupiter + + + junit-jupiter-params + org.junit.jupiter + + + junit-jupiter-engine + org.junit.jupiter + + + + + + 16 + 16 + true + 3.8.1 + + diff --git a/docs/deltaspec.json b/docs/deltaspec.json deleted file mode 100644 index 8aefc021..00000000 --- a/docs/deltaspec.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "systemName": "train-ticket", - "version": "0.0.1", - "services": [ - { - "action": "MODIFY", - "id": "ms1111111", - "msName": "ts-service1", - "msPath": "train-ticket-microservices/ts-service1", - "startCommit": "abcdef123456789abcdef123456789", - "endCommit": "bbbbb44444444aaaaaaaa66666666", - "endpoints": [ - { - "action": "MODIFY", - "id": "endpoint1111111", - "api": "/api/v1/service1/routes", - "file": "train-ticket-microservices/ts-service1/src/main/java/com/cloudhubs/trainticket/service1/controller/RouteController.java", - "type": "@RequestMapping", - "httpMethod": "GET", - "methodName": "getRoutes", - "arguments": "[@RequestBody RouteInfo info, @RequestHeader HttpHeaders headers]", - "return": "java.util.List" - }, - { - "action": "ADD", - "id": "endpoint2222222", - "api": "/api/v1/service1/routes/{id}", - "file": "train-ticket-microservices/ts-service1/src/main/java/com/cloudhubs/trainticket/service1/controller/RouteController.java", - "type": "@RequestMapping", - "httpMethod": "GET", - "methodName": "getRouteById", - "arguments": "[@RequestBody RouteInfo info, @RequestHeader HttpHeaders headers]", - "return": "com.cloudhubs.trainticket.service1.entity.Route" - }, - { - "action": "REMOVE", - "id": "endpoint333333" - } - ], - "dependencies": { - "restDependencies": [ - { - "action": "ADD", - "id": "ms2222222", - "msName": "ts-service2", - "calls": [ - { - "action": "ADD", - "id": "endpoint4444444", - "sourceFile": "train-ticket-microservices/ts-service1/src/main/java/com/cloudhubs/trainticket/service1/service/impl/Service1Impl.java", - "sourceMethod": "methodNameWhereCallTookPlaceFrom" - } - ] - }, - { - "action": "REMOVE", - "id": "ms3333333" - } - ], - "dtoDependencies": [ - { - "action": "MODIFY", - "id": "ms4444444", - "msName": "ts-service4", - "sharedObjects": [ - { - "action": "REMOVE", - "id": "dto1234567" - } - ] - } - ], - "entityDependencies": [], - "constraintDependencies": [] - } - } - ] -} diff --git a/docs/index.html b/docs/index.html index 5ca3a4b7..06133fa3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -50,21 +50,9 @@

Project Moti

Setup

- - - - -

Prerequisites

-

- Before you begin, ensure you have met the following requirements: -

-

+ + +

Installation

@@ -79,23 +67,38 @@

Installation

Running the Project

-

- After the installation is complete, you can run the project by executing the following command: +

+ After the installation is complete, you can run the project using either Maven or by executing the JAR file:

    -
  • To Generate an IR: mvn clean compile -P run-ir
  • -
  • To Generate an Delta: mvn clean compile -P run-delta
  • -
  • To Generate an Merge: mvn clean compile -P run-merge
  • -
  • To Generate Historical Data: mvn clean compile -P run-history
  • +
  • To Generate Historical Data: +
      +
    • Using Maven: mvn clean package -P run-history
    • +
    • Using JAR: java -jar cimet2-data.jar
    • +
    +
-

+ + Ensure you have a configuration file named "config.json" present in the same directory as the JAR. A sample configuration file is below... + +
+ + {
+ "systemName": "systemName",
+ "repositoryURL": "http://gitRepository.git",
+ "baseBranch": "master"
+ } +
+

-

Footnotes

-

-

+

Demo

+ +
+ +

For more information and documentation, click here.

diff --git a/docs/ir-spec.json b/docs/ir-spec.json deleted file mode 100644 index 07dda351..00000000 --- a/docs/ir-spec.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "systemName": "train-ticket", - "version": "0.0.1", - "services": [ - { - "id": "ts-service1", - "msName": "ts-service1", - "msPath": "train-ticket-microservices/ts-service1", - "commitId": "abcdef123456789abcdef123456789", - "endpoints": [ - { - "id": "GET:ts-service1.getRoutes#123", - "api": "/api/v1/service1/routes", - "file": "train-ticket-microservices/ts-service1/src/main/java/com/cloudhubs/trainticket/service1/controller/RouteController.java", - "type": "@RequestMapping", - "httpMethod": "GET", - "methodName": "getRoutes", - "arguments": "[@RequestBody RouteInfo info, @RequestHeader HttpHeaders headers]", - "return": "java.util.List" - }, - ... - ], - "dependencies": { - "restDependencies": [ - { - "id": "ms2222222", - "msName": "ts-service2", - "calls": [ - { - "id": "endpoint222222", - "sourceFile": "train-ticket-microservices/ts-service1/src/main/java/com/cloudhubs/trainticket/service1/service/impl/Service1Impl.java", - "sourceMethod": "methodNameWhereCallTookPlaceFrom" - }, - ... - ] - } - ... - ], - "dtoDependencies": [ - { - "id": "ms3333333", - "msName": "ts-service3", - "sharedObjects": [...] - }, - ... - ], - "entityDependencies": [...], - "constraintDepenencies": [...], - ... - } - }, - ... - ] -} diff --git a/docs/setup-linux.html b/docs/setup-linux.html new file mode 100644 index 00000000..006626d9 --- /dev/null +++ b/docs/setup-linux.html @@ -0,0 +1,43 @@ + + + + + + CIMET2 - Linux Setup + + + + +
+

CIMET2 Linux Setup

+
+ +
+

Installing Git, Java, and Maven on Linux

+ +

Step 1: Install Git

+

Install Git using your package manager. For Ubuntu: sudo apt install git, or download from here.

+ +

Step 2: Install Java

+

Install OpenJDK: sudo apt install openjdk-17-jdk, or download from here.

+ +

Step 3: Install Maven

+

Install Maven: sudo apt install maven, or download from here.

+ +

Step 4: Running a JAR File

+

To run a JAR file, use the following command:

+

java -jar yourfile.jar

+
+ + + diff --git a/docs/setup-macos.html b/docs/setup-macos.html new file mode 100644 index 00000000..1d21fc23 --- /dev/null +++ b/docs/setup-macos.html @@ -0,0 +1,43 @@ + + + + + + CIMET2 - macOS Setup + + + + +
+

CIMET2 macOS Setup

+
+ +
+

Installing Git, Java, and Maven on macOS

+ +

Step 1: Install Git

+

Install Git using Homebrew by running: brew install git. You can also download from here.

+ +

Step 2: Install Java

+

Install Java using Homebrew: brew install java. Follow Oracle’s guide for manual installation.

+ +

Step 3: Install Maven

+

Install Maven using Homebrew: brew install maven, or download from here.

+ +

Step 4: Running a JAR File

+

To run a JAR file, use the following command:

+

java -jar yourfile.jar

+
+ + + diff --git a/docs/setup-windows.html b/docs/setup-windows.html new file mode 100644 index 00000000..d95f91f0 --- /dev/null +++ b/docs/setup-windows.html @@ -0,0 +1,43 @@ + + + + + + CIMET2 - Windows Setup + + + + +
+

CIMET2 Windows Setup

+
+ +
+

Installing Git, Java, and Maven on Windows

+ +

Step 1: Install Git

+

Download Git from here. Follow the installation instructions.

+ +

Step 2: Install Java

+

Download and install Java from here. Set the JAVA_HOME environment variable.

+ +

Step 3: Install Maven

+

Download Maven from here. Extract the files and add bin to your system's PATH.

+ +

Step 4: Running a JAR File

+

To run a JAR file, use the following command:

+

java -jar yourfile.jar

+
+ + + diff --git a/pom.xml b/pom.xml index 5de3304b..39395631 100644 --- a/pom.xml +++ b/pom.xml @@ -7,11 +7,12 @@ cimet 1.0-SNAPSHOT - 11 - 11 + 16 + 16 true 3.8.1 + jar @@ -36,6 +37,12 @@ gson 2.8.9 + + org.javatuples + javatuples + 1.2 + compile + org.eclipse.jgit org.eclipse.jgit @@ -134,6 +141,51 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + 16 + 16 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.2.0 + + + + true + ${mainClass} + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 3.7.1 + + + jar-with-dependencies + + + + ${mainClass} + + + + + + assemble-all + package + + single + + + + diff --git a/scripts/parser2.js b/scripts/parser2.js new file mode 100644 index 00000000..9a8d4b6b --- /dev/null +++ b/scripts/parser2.js @@ -0,0 +1,120 @@ +const babel = require("@babel/core"); +const traverse = require("@babel/traverse").default; +const t = require("@babel/types"); + +const jsCode = ` +'use strict'; + +/** + * @ngdoc service + * @name alertApp.dataService + * @description + * # dataService + * Factory in the alertApp. + */ +angular.module('oauthApp') + .factory('dataService', function ($http, $q) { + var userApi = '/user-service/'; + var taskApi = '/task-service/'; + var loggedInUserApi = '/api/loggedinuser/me'; + + var makeRestCall = function (url) { + return $http.get(url) + .then(function (response) { + if (typeof response.data === 'object') { + return response.data; + } else { + return $q.reject(response.data); + } + }, function (response) { + return $q.reject(response.data); + }); + }; + + return { + getAllUserData: function () { + return makeRestCall(userApi); + }, + getAllTaskData: function () { + return makeRestCall(taskApi); + }, + getUserDataByUserName: function (userName) { + return makeRestCall(userApi + userName); + }, + getTaskDataByTaskId: function (taskId) { + return makeRestCall(taskApi + taskId); + }, + getTaskDataByUserName: function (userName) { + return makeRestCall(taskApi + 'usertask' + '/' + userName); + }, + getLoggedInUser: function () { + return makeRestCall(loggedInUserApi); + } + }; + }); +`; + +// Transform the code to get the AST +const output = babel.transform(jsCode, { + ast: true, + code: false, + presets: ["@babel/preset-env"], +}); + +// Map to store variable values +const variableValues = {}; + +// Function to check if a call is an API call ($http.get or similar) +function isApiCall(node) { + return ( + t.isMemberExpression(node.callee) && + t.isIdentifier(node.callee.object, { name: "$http" }) && + (t.isIdentifier(node.callee.property, { name: "get" }) || + t.isIdentifier(node.callee.property, { name: "post" })) + ); +} + +function resolveValue(node) { + if (t.isLiteral(node)) { + // Return literal value + return node.value; + } else if (t.isIdentifier(node)) { + // Check if the variable is known + return variableValues[node.name] || '{?}'; + } else if (t.isBinaryExpression(node) && node.operator === "+") { + // Resolve both sides of the binary expression (concatenation) + const left = resolveValue(node.left); + const right = resolveValue(node.right); + return left + right; + } else if (node.type === "CallExpression") { + // Handle function calls as unknown + return '{?}'; + } else { + // Handle any other case as unknown + return '{?}'; + } +} + +// Traverse the AST +traverse(output.ast, { + // Track variable declarations + VariableDeclarator(path) { + const varName = path.node.id.name; + const init = path.node.init; + if (init && (t.isLiteral(init) || t.isBinaryExpression(init))) { + // Store the value of the variable (literals or binary expressions) + variableValues[varName] = resolveValue(init); + } + }, + + + CallExpression(path) { + if (isApiCall(path.node)) { + const urlArg = path.node.arguments[0]; // First argument (URL) + const resolvedUrl = resolveValue(urlArg); // Try to resolve the argument + + console.log(`Resolved URL: ${path.node.callee.object.name}.${path.node.callee.property.name} to ${resolvedUrl}`); + } + } + +}); \ No newline at end of file diff --git a/src/main/java/edu/university/ecs/lab/AllConfigsExcelRunner.java b/src/main/java/edu/university/ecs/lab/AllConfigsExcelRunner.java index 21ba64f4..f9da9628 100644 --- a/src/main/java/edu/university/ecs/lab/AllConfigsExcelRunner.java +++ b/src/main/java/edu/university/ecs/lab/AllConfigsExcelRunner.java @@ -4,6 +4,10 @@ import java.io.File; import java.io.IOException; +/** + * Runs excel output runner for all config files in valid_configs directory + * NOTE: Must change the ExcelOutputRunner class to take config filepath as input args + */ public class AllConfigsExcelRunner { public static void main(String[] args) throws IOException { diff --git a/src/main/java/edu/university/ecs/lab/common/config/Config.java b/src/main/java/edu/university/ecs/lab/common/config/Config.java index 185aa5b0..96ba210c 100644 --- a/src/main/java/edu/university/ecs/lab/common/config/Config.java +++ b/src/main/java/edu/university/ecs/lab/common/config/Config.java @@ -44,7 +44,7 @@ public Config(String systemName, String repositoryURL, String branch) throws Exc } /** - * This method + * Check that config file is valid and has all required fields */ private void validateConfig(String systemName, String repositoryURL, String branch) { diff --git a/src/main/java/edu/university/ecs/lab/common/error/Error.java b/src/main/java/edu/university/ecs/lab/common/error/Error.java index 2e35e460..3daa7e9c 100644 --- a/src/main/java/edu/university/ecs/lab/common/error/Error.java +++ b/src/main/java/edu/university/ecs/lab/common/error/Error.java @@ -22,7 +22,8 @@ public enum Error { INVALID_JSON_READ(9, "Unable to read JSON from file!"), INVALID_JSON_WRITE(10, "Unable to write JSON to file!"), JPARSE_FAILED(10, "Failed to parse Java Code!"), - INVALID_CONFIG(10, "Invalid configuration file!"); + INVALID_CONFIG(10, "Invalid configuration file!"), + MISSING_CONFIG(10, "Missing configuration file!"); /** * The unique error code identifying the error type. diff --git a/src/main/java/edu/university/ecs/lab/common/models/enums/EndpointTemplate.java b/src/main/java/edu/university/ecs/lab/common/models/enums/EndpointTemplate.java index fd1ef38e..fa90fcf4 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/enums/EndpointTemplate.java +++ b/src/main/java/edu/university/ecs/lab/common/models/enums/EndpointTemplate.java @@ -1,25 +1,20 @@ package edu.university.ecs.lab.common.models.enums; -import com.github.javaparser.ast.expr.AnnotationExpr; -import com.github.javaparser.ast.expr.MemberValuePair; -import com.github.javaparser.ast.expr.NormalAnnotationExpr; -import com.github.javaparser.ast.expr.SingleMemberAnnotationExpr; +import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.expr.*; import edu.university.ecs.lab.intermediate.utils.StringParserUtils; import lombok.Getter; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * Factory class for generating an endpoint template from annotations */ @Getter public class EndpointTemplate { -// GET_MAPPING("GetMapping", HttpMethod.GET), -// POST_MAPPING("PostMapping", HttpMethod.POST), -// DELETE_MAPPING("DeleteMapping", HttpMethod.DELETE), -// private static final EndpointTemplate PUT_MAPPING = new EndpointTemplate("PutMapping", HttpMethod.PUT); - public static final List ENDPOINT_ANNOTATIONS = Arrays.asList("RequestMapping", "GetMapping", "PutMapping", "PostMapping", "DeleteMapping", "PatchMapping"); private final HttpMethod httpMethod; private final String name; @@ -57,6 +52,10 @@ public EndpointTemplate(AnnotationExpr requestMapping, AnnotationExpr endpointMa } } else if (endpointMapping instanceof SingleMemberAnnotationExpr) { url = endpointMapping.asSingleMemberAnnotationExpr().getMemberValue().toString().replaceAll("\"", ""); + } else if(endpointMapping instanceof MarkerAnnotationExpr) { + if(preUrl.isEmpty()) { + url = "/"; + } } if(finalHttpMethod == HttpMethod.ALL) { @@ -82,16 +81,23 @@ public EndpointTemplate(AnnotationExpr requestMapping, AnnotationExpr endpointMa // Replace any double slashes finalURL = finalURL.replaceAll("//", "/"); // If it ends with a slash remove it - finalURL = finalURL.endsWith("/") ? finalURL.substring(0, finalURL.length() - 1) : finalURL; + finalURL = finalURL.endsWith("/") && !finalURL.equals("/") ? finalURL.substring(0, finalURL.length() - 1) : finalURL; + // Get query Parameters + this.httpMethod = finalHttpMethod; this.name = endpointMapping.getNameAsString(); - this.url = finalURL; + this.url = simplifyEndpointURL(finalURL); } - + /** + * Method to get http method from mapping + * + * @param mapping mapping string for a given method + * @return HttpMethod object of same method type + */ private static HttpMethod httpFromMapping(String mapping) { switch (mapping) { case "GetMapping": @@ -115,6 +121,13 @@ private static HttpMethod httpFromMapping(String mapping) { } + /** + * Method to get endpoint path from annotations + * + * @param ae annotation expression from method + * @param url string formatted as a url + * @return endpoint path/url from annotation expression + */ public static String getPathFromAnnotation(AnnotationExpr ae, String url) { // Annotations of type @Mapping("/endpoint") if (ae.isSingleMemberAnnotationExpr()) { diff --git a/src/main/java/edu/university/ecs/lab/common/models/enums/FileType.java b/src/main/java/edu/university/ecs/lab/common/models/enums/FileType.java index 217a2b45..5b3d69e2 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/enums/FileType.java +++ b/src/main/java/edu/university/ecs/lab/common/models/enums/FileType.java @@ -1,5 +1,8 @@ package edu.university.ecs.lab.common.models.enums; +/** + * File types enum + */ public enum FileType { JCLASS, CONFIG, diff --git a/src/main/java/edu/university/ecs/lab/common/models/enums/RestCallTemplate.java b/src/main/java/edu/university/ecs/lab/common/models/enums/RestCallTemplate.java index 48eb2c0c..ecab9dc1 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/enums/RestCallTemplate.java +++ b/src/main/java/edu/university/ecs/lab/common/models/enums/RestCallTemplate.java @@ -5,6 +5,7 @@ import com.github.javaparser.ast.body.FieldDeclaration; import com.github.javaparser.ast.body.VariableDeclarator; import com.github.javaparser.ast.expr.*; +import edu.university.ecs.lab.common.models.ir.MethodCall; import edu.university.ecs.lab.intermediate.utils.StringParserUtils; import javassist.expr.Expr; import lombok.Getter; @@ -20,8 +21,8 @@ */ @Getter public class RestCallTemplate { - public static final Set REST_OBJECTS = Set.of("RestTemplate", "OAuth2RestOperations", "OAuth2RestTemplate"); - public static final Set REST_METHODS = Set.of("getForObject", "postForObject", "patchForObject", "put", "delete", "exchange"); + public static final Set REST_OBJECTS = Set.of("RestTemplate", "OAuth2RestOperations", "OAuth2RestTemplate", "WebClient"); + public static final Set REST_METHODS = Set.of("getForObject", "postForObject", "patchForObject", "put", "delete", "exchange", "get", "post", "options", "patch"); private static final String UNKNOWN_VALUE = "{?}"; private final String url; @@ -29,10 +30,10 @@ public class RestCallTemplate { private final CompilationUnit cu; private final MethodCallExpr mce; - public RestCallTemplate(MethodCallExpr mce, CompilationUnit cu) { + public RestCallTemplate(MethodCallExpr mce, MethodCall mc, CompilationUnit cu) { this.cu = cu; this.mce = mce; - this.url = mce.getArguments().get(0).toString().isEmpty() ? "" : cleanURL(parseURL(mce.getArguments().get(0))); + this.url = simplifyEndpointURL(preParseURL(mce, mc)); this.httpMethod = getHttpFromName(mce); } @@ -46,10 +47,13 @@ public HttpMethod getHttpFromName(MethodCallExpr mce) { String methodName = mce.getNameAsString(); switch (methodName) { case "getForObject": + case "get": return HttpMethod.GET; case "postForObject": + case "post": return HttpMethod.POST; case "patchForObject": + case "patch": return HttpMethod.PATCH; case "put": return HttpMethod.PUT; @@ -146,6 +150,12 @@ private String backupParseURL(Expression exp) { } + /** + * Shorten URLs to only endpoint query + * + * @param str full url + * @return url query + */ private static String cleanURL(String str) { str = str.replace("http://", ""); str = str.replace("https://", ""); @@ -156,11 +166,7 @@ private static String cleanURL(String str) { str = str.substring(backslashNdx); } -// int questionNdx = str.indexOf("?"); -// if (questionNdx > 0) { -// str = str.substring(0, questionNdx); -// } - + // Remove any trailing quotes if (str.endsWith("\"")) { str = str.substring(0, str.length() - 1); } @@ -186,15 +192,30 @@ private String parseFieldValue(String fieldName) { return ""; } -// private String parseNameValue(Expression mceScope, String name) { -// for (VariableDeclarationExpr vdc : mceScope.findAll(VariableDeclarationExpr.class)) { -// for(VariableDeclarator vd : vdc.getVariables()) { -// if(vd.getNameAsString().equals(name)) { -// -// } -// } -// } -// -// return ""; -// } + + private String preParseURL(MethodCallExpr mce, MethodCall mc) { + + // Nuance for webclient with method appender pattern + if(mc.getObjectType().equals("WebClient")) { + if(mce.getParentNode().isPresent()) { + if(mce.getParentNode().get() instanceof MethodCallExpr pmce) { + return pmce.getArguments().get(0).toString().isEmpty() ? "" : cleanURL(parseURL(pmce.getArguments().get(0))); + } + } + } else { + return mce.getArguments().get(0).toString().isEmpty() ? "" : cleanURL(parseURL(mce.getArguments().get(0))); + } + + return ""; + } + + /** + * Simplifies all path arguments to {?}. + * + * @param url the endpoint URL + * @return the simplified endpoint URL + */ + public static String simplifyEndpointURL(String url) { + return url.replaceAll("\\{[^{}]*\\}", "{?}"); + } } diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Annotation.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Annotation.java index a7fc42b8..171b9f27 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/Annotation.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Annotation.java @@ -1,25 +1,46 @@ package edu.university.ecs.lab.common.models.ir; +import com.github.javaparser.ast.expr.*; +import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.reflect.TypeToken; import edu.university.ecs.lab.common.models.serialization.JsonSerializable; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + /** * Represents an annotation in Java */ @Data @EqualsAndHashCode public class Annotation extends Node { - /** - * The contents of the annotation * - */ - protected String contents; - public Annotation(String name, String packageAndClassName, String contents) { + private Map attributes; + + public Annotation(AnnotationExpr annotationExpr, String packageAndClassName) { + this.name = annotationExpr.getNameAsString(); + this.packageAndClassName = packageAndClassName; + this.attributes = parseAttributes(annotationExpr); + } + + public Annotation(String name, String packageAndClassName, HashMap attributes) { this.name = name; this.packageAndClassName = packageAndClassName; - this.contents = contents; + this.attributes = attributes; + } + + /** + * Get contents of annotation object + * + * @return comma-delimmited list of annotation content key-value pairs + */ + public String getContents() { + return getAttributes().entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining(",")); } /** @@ -28,11 +49,41 @@ public Annotation(String name, String packageAndClassName, String contents) { @Override public JsonObject toJsonObject() { JsonObject jsonObject = new JsonObject(); + Gson gson = new Gson(); jsonObject.addProperty("name", getName()); jsonObject.addProperty("packageAndClassName", getPackageAndClassName()); - jsonObject.addProperty("contents", getContents()); + JsonElement attributesJson = gson.toJsonTree(attributes, new TypeToken>(){}.getType()); + jsonObject.add("attributes", attributesJson); return jsonObject; } + + /** + * Map attributes from annotation expression + * + * @param annotationExpr annotation expression object to parse + * @return map of annotation attributes and their values + */ + private static HashMap parseAttributes(AnnotationExpr annotationExpr) { + HashMap attributes = new HashMap<>(); + + if(annotationExpr instanceof MarkerAnnotationExpr) { + return attributes; + } else if (annotationExpr instanceof SingleMemberAnnotationExpr smAnnotationExpr) { + if(smAnnotationExpr.getMemberValue() instanceof StringLiteralExpr sle) { + attributes.put("default", sle.asString()); + } else { + return attributes; + } + } else if (annotationExpr instanceof NormalAnnotationExpr nAnnotationExpr) { + for(MemberValuePair mvp : nAnnotationExpr.getPairs()) { + if(mvp.getValue() instanceof StringLiteralExpr sle) { + attributes.put(mvp.getNameAsString(), sle.asString()); + } + } + } + + return attributes; + } } diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/ConfigFile.java b/src/main/java/edu/university/ecs/lab/common/models/ir/ConfigFile.java index 67b074fe..971bb86a 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/ConfigFile.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/ConfigFile.java @@ -5,6 +5,9 @@ import edu.university.ecs.lab.common.models.serialization.JsonSerializable; import lombok.Getter; +/** + * Represents a project configuration file + */ @Getter public class ConfigFile extends ProjectFile implements JsonSerializable { private final JsonObject data; diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Endpoint.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Endpoint.java index 2d48b81c..664fdc4a 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/Endpoint.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Endpoint.java @@ -1,11 +1,13 @@ package edu.university.ecs.lab.common.models.ir; +import com.google.gson.Gson; import com.google.gson.JsonObject; import edu.university.ecs.lab.common.models.enums.HttpMethod; import edu.university.ecs.lab.common.models.serialization.JsonSerializable; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.HashSet; import java.util.Set; import java.util.List; @@ -29,7 +31,8 @@ public class Endpoint extends Method { private HttpMethod httpMethod; - public Endpoint(String methodName, String packageName, Set parameters, String returnType, Set annotations, String microserviceName, + + public Endpoint(String methodName, String packageName, Set parameters, String returnType, Set annotations, String microserviceName, String className) { super(methodName, packageName, parameters, returnType, annotations, microserviceName, className); } @@ -53,4 +56,5 @@ public JsonObject toJsonObject() { return jsonObject; } + } diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Flow.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Flow.java index 21e16e3a..afa51337 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/Flow.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Flow.java @@ -23,6 +23,9 @@ public class Flow implements JsonSerializable { private JClass repository; private Method repositoryMethod; + /** + * Create JSON object from flow object + */ @Override public JsonObject toJsonObject() { JsonObject jsonObject = new JsonObject(); @@ -41,6 +44,11 @@ public JsonObject toJsonObject() { return jsonObject; } + /** + * Create JSON object from flow object with only names + * + * @return flow JSON object + */ public JsonObject toSmallJsonObject() { JsonObject jsonObject = new JsonObject(); diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/JClass.java b/src/main/java/edu/university/ecs/lab/common/models/ir/JClass.java index 6a46a8da..c1ebc9bf 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/JClass.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/JClass.java @@ -9,6 +9,7 @@ import lombok.EqualsAndHashCode; import lombok.NonNull; +import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import java.util.List; @@ -49,11 +50,25 @@ public class JClass extends ProjectFile implements JsonSerializable { private Set annotations; /** - * Set of method invocations made from within this class + * List of method invocations made from within this class */ - private Set methodCalls; + private List methodCalls; - public JClass(String name, String path, String packageName, ClassRole classRole, Set methods, Set fields, Set classAnnotations, Set methodCalls, Set implementedTypes) { + + public JClass(String name, String path, String packageName, ClassRole classRole) { + this.name = name; + this.packageName = packageName; + this.path = path; + this.classRole = classRole; + this.methods = new HashSet<>(); + this.fields = new HashSet<>(); + this.annotations = new HashSet<>(); + this.methodCalls = new ArrayList<>(); + this.implementedTypes = new HashSet<>(); + this.fileType = FileType.JCLASS; + } + + public JClass(String name, String path, String packageName, ClassRole classRole, Set methods, Set fields, Set classAnnotations, List methodCalls, Set implementedTypes) { this.name = name; this.packageName = packageName; this.path = path; @@ -105,11 +120,9 @@ public Set getEndpoints() { * see {@link RestCall} * @return set of all restCalls */ - public Set getRestCalls() { - if((!getClassRole().equals(ClassRole.SERVICE) && !getClassRole().equals(ClassRole.FEIGN_CLIENT)) || getMethodCalls().isEmpty()) { - return new HashSet<>(); - } - return methodCalls.stream().filter(methodCall -> methodCall instanceof RestCall).map(methodCall -> (RestCall) methodCall).collect(Collectors.toUnmodifiableSet()); + public List getRestCalls() { + + return methodCalls.stream().filter(methodCall -> methodCall instanceof RestCall).map(methodCall -> (RestCall) methodCall).collect(Collectors.toUnmodifiableList()); } /** diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Method.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Method.java index 98518002..60852ea3 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/Method.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Method.java @@ -1,5 +1,7 @@ package edu.university.ecs.lab.common.models.ir; +import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.MethodDeclaration; import com.google.gson.JsonObject; import edu.university.ecs.lab.common.models.serialization.JsonSerializable; import lombok.Data; @@ -21,7 +23,7 @@ public class Method extends Node { /** * Set of fields representing parameters */ - protected Set parameters; + protected Set parameters; /** * Java return type of the method @@ -43,7 +45,7 @@ public class Method extends Node { */ protected String className; - public Method(String name, String packageAndClassName, Set parameters, String typeAsString, Set annotations, String microserviceName, + public Method(String name, String packageAndClassName, Set parameters, String typeAsString, Set annotations, String microserviceName, String className) { this.name = name; this.packageAndClassName = packageAndClassName; @@ -54,6 +56,12 @@ public Method(String name, String packageAndClassName, Set parameters, St this.className = className; } + public Method(MethodDeclaration methodDeclaration) { + this.name = methodDeclaration.getNameAsString(); + this.packageAndClassName = methodDeclaration.getClass().getPackageName() + "." + methodDeclaration.getClass().getName(); + this.parameters = parseParameters(methodDeclaration.getParameters()); + } + /** * see {@link JsonSerializable#toJsonObject()} */ @@ -72,4 +80,21 @@ public JsonObject toJsonObject() { return jsonObject; } + /** + * Get set of parameters from node list + * + * @param parameters Node list of javaparser parameter objects + * @return set of parameter objects + */ + private Set parseParameters(NodeList parameters) { + HashSet parameterSet = new HashSet<>(); + + for(com.github.javaparser.ast.body.Parameter parameter : parameters) { + parameterSet.add(new Parameter(parameter, getPackageAndClassName())); + } + + + return parameterSet; + } + } diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/MethodCall.java b/src/main/java/edu/university/ecs/lab/common/models/ir/MethodCall.java index f1ba2703..2c18fb26 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/MethodCall.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/MethodCall.java @@ -76,6 +76,13 @@ public JsonObject toJsonObject() { return jsonObject; } + /** + * Checks if a method call matches a given method + * + * @param methodCall method call object to match + * @param method method object to match + * @return true if method call and method match, false otherwise + */ public static boolean matchMethod(MethodCall methodCall, Method method) { return methodCall.microserviceName.equals(method.microserviceName) && methodCall.objectType.equals(method.className) && methodCall.name.equals(method.name); diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Microservice.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Microservice.java index a3f6fd32..9b2aedf8 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/Microservice.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Microservice.java @@ -9,6 +9,7 @@ import lombok.EqualsAndHashCode; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -88,7 +89,6 @@ public JsonObject toJsonObject() { jsonObject.addProperty("path", path); jsonObject.add("controllers", JsonSerializable.toJsonArray(controllers)); jsonObject.add("entities", JsonSerializable.toJsonArray(entities)); -// jsonObject.add("embeddables", JsonSerializable.toJsonArray(embeddables)); jsonObject.add("feignClients", JsonSerializable.toJsonArray(feignClients)); jsonObject.add("services", JsonSerializable.toJsonArray(services)); jsonObject.add("repositories", JsonSerializable.toJsonArray(repositories)); @@ -98,6 +98,9 @@ public JsonObject toJsonObject() { } + /** + * see {@link JsonSerializable#toJsonArray(Iterable)} + */ private static JsonArray toJsonArray(Iterable list) { JsonArray jsonArray = new JsonArray(); for (JsonObject object : list) { @@ -272,28 +275,42 @@ public Set getAllFiles() { return set; } - public Set getRestCalls () { - Set restCalls = new HashSet<>(); - restCalls.addAll(getServices().stream() - .flatMap(service -> service.getRestCalls().stream()) - .collect(Collectors.toSet())); - restCalls.addAll(getFeignClients().stream() - .flatMap(client -> client.getRestCalls().stream()) - .collect(Collectors.toSet())); - return restCalls; + /** + * This method returns all rest calls of a microservice + * + * @return the list of all rest calls + */ + public List getRestCalls () { + return getClasses().stream() + .flatMap(jClass -> jClass.getRestCalls().stream()).collect(Collectors.toList()); } + /** + * This method returns all endpoints of a microservice + * + * @return the set of all endpoints + */ public Set getEndpoints () { - return this.controllers.stream().flatMap(controller -> + return getControllers().stream().flatMap(controller -> controller.getEndpoints().stream()).collect(Collectors.toSet()); } + /** + * This method returns all method calls of a microservice + * + * @return the set of all method calls + */ public Set getMethodCalls () { - return this.getClasses().stream().flatMap(jClass -> jClass.getMethodCalls().stream()).collect(Collectors.toSet()); + return getClasses().stream().flatMap(jClass -> jClass.getMethodCalls().stream()).collect(Collectors.toSet()); } + /** + * This method returns all methods of a microservice + * + * @return the set of all methods + */ public Set getMethods () { - return this.getClasses().stream().flatMap(jClass -> jClass.getMethods().stream()).collect(Collectors.toSet()); + return getClasses().stream().flatMap(jClass -> jClass.getMethods().stream()).collect(Collectors.toSet()); } diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/MicroserviceSystem.java b/src/main/java/edu/university/ecs/lab/common/models/ir/MicroserviceSystem.java index 1d43ef20..83a7c2d5 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/MicroserviceSystem.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/MicroserviceSystem.java @@ -105,6 +105,12 @@ public void adopt(Microservice microservice) { } + /** + * Get the class of a given endpoint + * + * @param path endpoint + * @return class that endpoint is in + */ public JClass findClass(String path){ JClass returnClass = null; returnClass = getMicroservices().stream().flatMap(m -> m.getClasses().stream()).filter(c -> c.getPath().equals(path)).findFirst().orElse(null); @@ -115,6 +121,12 @@ public JClass findClass(String path){ return returnClass; } + /** + * Get the file of a given endpoint + * + * @param path endpoint + * @return file that endpoint is in + */ public ProjectFile findFile(String path){ ProjectFile returnFile = null; returnFile = getMicroservices().stream().flatMap(m -> m.getAllFiles().stream()).filter(c -> c.getPath().equals(path)).findFirst().orElse(null); diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/Parameter.java b/src/main/java/edu/university/ecs/lab/common/models/ir/Parameter.java new file mode 100644 index 00000000..81a1b21b --- /dev/null +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/Parameter.java @@ -0,0 +1,52 @@ +package edu.university.ecs.lab.common.models.ir; + +import com.google.gson.JsonObject; +import edu.university.ecs.lab.common.models.serialization.JsonSerializable; +import lombok.Data; + +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Represents a method call parameter + */ +@Data +public class Parameter extends Node implements JsonSerializable { + + /** + * Java class type of the class variable e.g. String + */ + private String type; + + private Set annotations; + + public Parameter(String name, String packageAndClassName, String type, Set annotations) { + this.name = name; + this.packageAndClassName = packageAndClassName; + this.type = type; + this.annotations = annotations; + } + + public Parameter(com.github.javaparser.ast.body.Parameter parameter, String packageAndClassName) { + this.name = parameter.getNameAsString(); + this.type = parameter.getTypeAsString(); + this.packageAndClassName = packageAndClassName; + this.annotations = parameter.getAnnotations().stream().map(annotationExpr -> new Annotation(annotationExpr, packageAndClassName)).collect(Collectors.toSet()); + } + + + /** + * see {@link JsonSerializable#toJsonObject()} + */ + @Override + public JsonObject toJsonObject() { + JsonObject jsonObject = new JsonObject(); + + jsonObject.addProperty("name", getName()); + jsonObject.addProperty("packageAndClassName", getPackageAndClassName()); + jsonObject.addProperty("type", getType()); + jsonObject.add("annotations", JsonSerializable.toJsonArray(annotations)); + + return jsonObject; + } +} diff --git a/src/main/java/edu/university/ecs/lab/common/models/ir/RestCall.java b/src/main/java/edu/university/ecs/lab/common/models/ir/RestCall.java index 9a800a06..c8d28fdd 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/ir/RestCall.java +++ b/src/main/java/edu/university/ecs/lab/common/models/ir/RestCall.java @@ -1,11 +1,15 @@ package edu.university.ecs.lab.common.models.ir; +import com.google.gson.Gson; import com.google.gson.JsonObject; import edu.university.ecs.lab.common.models.enums.HttpMethod; import edu.university.ecs.lab.common.models.serialization.JsonSerializable; +import edu.university.ecs.lab.common.utils.JsonReadWriteUtils; import lombok.Data; import lombok.EqualsAndHashCode; +import java.util.Set; + /** * Represents an extension of a method call. A rest call exists at the service level and represents @@ -27,6 +31,8 @@ public class RestCall extends MethodCall { private HttpMethod httpMethod; + + public RestCall(String methodName, String packageAndClassName, String objectType, String objectName, String calledFrom, String parameterContents, String microserviceName, String className) { super(methodName, packageAndClassName, objectType, objectName, calledFrom, parameterContents, @@ -52,11 +58,55 @@ public JsonObject toJsonObject() { return jsonObject; } + /** + * Checks if a rest call matches a given endpoint + * + * @param restcall rest call to match + * @param endpoint endpoint to match + * @return true if rest call and enpoint match, false otherwise + */ public static boolean matchEndpoint(RestCall restcall, Endpoint endpoint){ if(restcall.getMicroserviceName().equals(endpoint.getMicroserviceName())){ return false; } - return restcall.getUrl().equals(endpoint.getUrl()) && restcall.getHttpMethod().equals(endpoint.getHttpMethod()); + int queryParamIndex = restcall.getUrl().replace("{?}", "temp").indexOf("?"); + String baseURL = queryParamIndex == -1 ? restcall.getUrl() : restcall.getUrl().substring(0, queryParamIndex); + return baseURL.equals(endpoint.getUrl()) && (restcall.getHttpMethod().equals(endpoint.getHttpMethod()) || endpoint.getHttpMethod().equals(HttpMethod.ALL)) && matchQueryParams(restcall, endpoint, queryParamIndex); + } + + /** + * Checks if rest call parameters match parameters for the target endpoint + * + * @param restCall rest call to match + * @param endpoint endpoint to match + * @param queryParamIndex string index at which query parameters start + * @return true if parameters match, false otherwise + */ + private static boolean matchQueryParams(RestCall restCall, Endpoint endpoint, int queryParamIndex) { + for(Parameter parameter : endpoint.getParameters()) { + for(Annotation annotation : parameter.getAnnotations()) { + if(annotation.getName().equals("RequestParam")) { + String queryParameterName = ""; + if(annotation.getAttributes().containsKey("default")) { + queryParameterName = annotation.getAttributes().get("default"); + } else if(annotation.getAttributes().containsKey("name")) { + if(annotation.getAttributes().containsKey("required") + && annotation.getAttributes().get("required").equals("false")) { + continue; + } + queryParameterName = annotation.getAttributes().get("name"); + } else { + queryParameterName = parameter.getName(); + } + + if(!restCall.getUrl().substring(queryParamIndex + 1, restCall.getUrl().length()).contains(queryParameterName + "=")) { + return false; + } + } + } + } + return true; } + } diff --git a/src/main/java/edu/university/ecs/lab/common/models/sdg/DependencyGraphI.java b/src/main/java/edu/university/ecs/lab/common/models/sdg/DependencyGraphI.java index 02a2e998..c653b046 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/sdg/DependencyGraphI.java +++ b/src/main/java/edu/university/ecs/lab/common/models/sdg/DependencyGraphI.java @@ -11,6 +11,9 @@ import java.util.Set; import java.util.stream.Collectors; +/** + * Represents an object dependency graph with dependent vertices connected by edges + */ public interface DependencyGraphI extends Graph, JsonSerializable { /** @@ -31,6 +34,11 @@ public interface DependencyGraphI extends Graph, JsonSerializable { */ boolean isMultigraph(); + /** + * Method to get adjacency list of the entire graph + * + * @return adjacency list of entire graph + */ default Map> getAdjacency() { return this.vertexSet().stream() .collect(Collectors.toMap( @@ -39,13 +47,21 @@ default Map> getAdjacency() { )); } + /** + * Method to get addjacency list of a given vertex + * + * @param vertex vertex to get adjacency list of + * @return adjacency list for given vertex + */ default Set getAdjacency(V vertex) { return this.outgoingEdgesOf(vertex).stream() .map(this::getEdgeTarget) .collect(Collectors.toSet()); } - + /** + * see {@link JsonSerializable#toJsonObject()} + */ default JsonObject toJsonObject() { JsonObject jsonObject = new JsonObject(); diff --git a/src/main/java/edu/university/ecs/lab/common/models/sdg/MethodDependencyGraph.java b/src/main/java/edu/university/ecs/lab/common/models/sdg/MethodDependencyGraph.java index d2bd7160..93be1597 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/sdg/MethodDependencyGraph.java +++ b/src/main/java/edu/university/ecs/lab/common/models/sdg/MethodDependencyGraph.java @@ -13,23 +13,48 @@ import java.util.Map; import java.util.Set; +/** + * Represents a dependency graph for service methods + */ @Getter public class MethodDependencyGraph extends DirectedWeightedPseudograph implements DependencyGraphI { + /** + * Represents the name of the graph + */ private final String label; + /** + * The timestamp of the current Network graph + * (i.e. the commit ID that the Network graph represents) + */ private final String timestamp; + /** + * Whether the edges are interpreted as directed + */ private final boolean directed = true; + /** + * Whether several edges between source and target are allowed + */ private final boolean multigraph = false; + /** + * Create a new method dependency graph from a given microservice system + * + * @param microserviceSystem microservice system from which to derive method dependency graph + */ public MethodDependencyGraph(MicroserviceSystem microserviceSystem) { super(DefaultWeightedEdge.class); this.label = microserviceSystem.getName(); this.timestamp = microserviceSystem.getCommitID(); + // Map of method details to method objects Map methods = new HashMap<>(); + + // Set of all method calls Set methodCalls = new HashSet<>(); + // Add method calls to set, map methods to microservice and class names microserviceSystem.getMicroservices() .forEach(ms -> { methodCalls.addAll(ms.getMethodCalls()); @@ -39,6 +64,7 @@ public MethodDependencyGraph(MicroserviceSystem microserviceSystem) { methods.put(method.getMicroserviceName() + method.getClassName() + method.getName(), method); });}); + // Add method/method call pairs to method dependency graph methods.values().forEach(targetMethod -> methodCalls.forEach(methodCall -> { Method sourceMethod = methods.get(methodCall.getMicroserviceName()+methodCall.getClassName()+methodCall.getCalledFrom()); @@ -59,6 +85,12 @@ public MethodDependencyGraph(MicroserviceSystem microserviceSystem) { } + /** + * Method to add and edge or update its weight if it already exists + * + * @param source method making the call + * @param target method being called + */ private void addUpdateEdge(Method source, Method target) { DefaultWeightedEdge edge = this.getEdge(source, target); if (edge == null) { @@ -93,6 +125,10 @@ public JsonObject toJsonObject() { return jsonObject; } + + /** + * Class to serialize a method as a json object + */ public static class MethodSerializer implements JsonSerializer { @Override public JsonElement serialize(Method method, Type type, JsonSerializationContext jsonSerializationContext) { @@ -101,6 +137,10 @@ public JsonElement serialize(Method method, Type type, JsonSerializationContext return jsonObject; } } + + /** + * Class to serialize an endpoint as a json object + */ public static class EndpointSerializer implements JsonSerializer { @Override public JsonElement serialize(Endpoint endpoint, Type type, JsonSerializationContext jsonSerializationContext) { @@ -110,6 +150,9 @@ public JsonElement serialize(Endpoint endpoint, Type type, JsonSerializationCont } } + /** + * Class to serialize an endpoint as a json object + */ public class MethodCallEdgeSerializer implements JsonSerializer { @Override public JsonElement serialize(DefaultWeightedEdge edge, Type typeOfSrc, JsonSerializationContext context) { diff --git a/src/main/java/edu/university/ecs/lab/common/models/sdg/ServiceDependencyGraph.java b/src/main/java/edu/university/ecs/lab/common/models/sdg/ServiceDependencyGraph.java index 218f95a3..75379571 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/sdg/ServiceDependencyGraph.java +++ b/src/main/java/edu/university/ecs/lab/common/models/sdg/ServiceDependencyGraph.java @@ -17,9 +17,23 @@ @Getter public class ServiceDependencyGraph extends DirectedWeightedMultigraph implements JsonSerializable, DependencyGraphI { + + /** + * Represents the name of the graph + */ private final String label; + /** + * The timestamp of the current Network graph + * (i.e. the commit ID that the Network graph represents) + */ private final String timestamp; + /** + * Whether the edges are interpreted as directed + */ private final boolean directed = true; + /** + * Whether several edges between source and target are allowed + */ private final boolean multigraph = true; /** @@ -61,6 +75,7 @@ public ServiceDependencyGraph(MicroserviceSystem microserviceSystem) { List restCalls = new ArrayList<>(); List endpoints = new ArrayList<>(); + // Add microservices, rest calls, and enpoints to respective lists, add microservice to graph as a vertex microserviceSystem.getMicroservices().forEach(microservice -> { ms.put(microservice.getName(), microservice); this.addVertex(microservice); @@ -70,6 +85,7 @@ public ServiceDependencyGraph(MicroserviceSystem microserviceSystem) { List> edgesList = new ArrayList<>(); + // Add services with matching rest call/endpoint pairs to edges list for (RestCall restCall : restCalls) { for (Endpoint endpoint : endpoints) { if (RestCall.matchEndpoint(restCall, endpoint)) { @@ -78,6 +94,7 @@ public ServiceDependencyGraph(MicroserviceSystem microserviceSystem) { } } + // Add edges to map edgesList.stream() .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())).forEach((edgeData, value) -> { RestCallEdge edge = this.addEdge(ms.get(edgeData.get(0)), ms.get(edgeData.get(1))); @@ -86,6 +103,9 @@ public ServiceDependencyGraph(MicroserviceSystem microserviceSystem) { }); } + /** + * Class to serialize a microservice as a json object + */ public static class MicroserviceSerializer implements JsonSerializer { @Override public JsonElement serialize(Microservice microservice, Type type, JsonSerializationContext jsonSerializationContext) { @@ -95,6 +115,9 @@ public JsonElement serialize(Microservice microservice, Type type, JsonSerializa } } + /** + * Class to serialize a rest call as a json object + */ public class RestCallEdgeSerializer implements JsonSerializer { @Override public JsonElement serialize(RestCallEdge edge, Type typeOfSrc, JsonSerializationContext context) { diff --git a/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodCallDeserializer.java b/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodCallDeserializer.java index 7c5b344b..289d1ffc 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodCallDeserializer.java +++ b/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodCallDeserializer.java @@ -5,6 +5,8 @@ import edu.university.ecs.lab.common.models.ir.RestCall; import edu.university.ecs.lab.common.models.enums.HttpMethod; import java.lang.reflect.Type; +import java.util.HashSet; +import java.util.Set; /** * Class for deserializing a MethodCall when using Gson @@ -42,6 +44,8 @@ private RestCall jsonToRestCall(JsonObject json) throws JsonParseException { String httpMethod = json.get("httpMethod").getAsString(); + + return new RestCall(methodCall, url, HttpMethod.valueOf(httpMethod)); } } diff --git a/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodDeserializer.java b/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodDeserializer.java index b24023bb..be163af8 100644 --- a/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodDeserializer.java +++ b/src/main/java/edu/university/ecs/lab/common/models/serialization/MethodDeserializer.java @@ -1,10 +1,7 @@ package edu.university.ecs.lab.common.models.serialization; import com.google.gson.*; -import edu.university.ecs.lab.common.models.ir.Annotation; -import edu.university.ecs.lab.common.models.ir.Endpoint; -import edu.university.ecs.lab.common.models.ir.Field; -import edu.university.ecs.lab.common.models.ir.Method; +import edu.university.ecs.lab.common.models.ir.*; import edu.university.ecs.lab.common.models.enums.HttpMethod; import java.lang.reflect.Type; import java.util.ArrayList; @@ -38,11 +35,11 @@ private Method jsonToMethod(JsonObject json, JsonDeserializationContext context) } method.setAnnotations(annotations); - Set fields = new HashSet(); + Set parameters = new HashSet<>(); for (JsonElement fieldJson : json.get("parameters").getAsJsonArray()) { - fields.add(context.deserialize(fieldJson, Field.class)); + parameters.add(context.deserialize(fieldJson, Parameter.class)); } - method.setParameters(fields); + method.setParameters(parameters); method.setPackageAndClassName(json.get("packageAndClassName").getAsString()); method.setMicroserviceName(json.get("microserviceName").getAsString()); method.setClassName(json.get("className").getAsString()); diff --git a/src/main/java/edu/university/ecs/lab/common/services/GitService.java b/src/main/java/edu/university/ecs/lab/common/services/GitService.java index 1f5ced35..9e72a04c 100644 --- a/src/main/java/edu/university/ecs/lab/common/services/GitService.java +++ b/src/main/java/edu/university/ecs/lab/common/services/GitService.java @@ -7,86 +7,61 @@ import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.ResetCommand; import org.eclipse.jgit.diff.DiffEntry; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.ObjectReader; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.*; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.treewalk.TreeWalk; import java.io.File; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.stream.Collectors; /** - * Service for managing local repository including the cloning - * and resetting the current commit. + * Service to perform Git opperations */ public class GitService { private static final int EXIT_SUCCESS = 0; private static final String HEAD_COMMIT = "HEAD"; - /** - * Configuration file path - */ private final Config config; - - /** - * Repository object for jgit usage - */ private final Repository repository; - /** - * Instantiation of this service will result in the following - * 1.) output and clone directories will be created or validated - * 2.) Configuration file will be read and validated by it's constructor - * 3.) The repository in config will be cloned or validated - * - * @param configPath + * Create a Git service object from a project configuration file + * + * @param configPath path to project configuration file */ public GitService(String configPath) { - // 1.) Read config this.config = ConfigUtil.readConfig(configPath); - - // 2.) Make the output and clone directory FileUtils.makeDirs(); - - // 3.) Clone the repository cloneRemote(); - - // If clone was successful we can now set repo and reset local repo to config base commit this.repository = initRepository(); - } - /** - * This method clones a remote repository to the local file system. Postcondition: the repository - * has been cloned to the local file system. - * + * Method to clone a repository */ public void cloneRemote() { - // Quietly return assuming cloning already took place String repositoryPath = FileUtils.getRepositoryPath(config.getRepoName()); - // Quietly return assuming cloning already took place + // Check if repository was already cloned if (new File(repositoryPath).exists()) { return; } + // Create and execute operating system process to clone repository try { ProcessBuilder processBuilder = new ProcessBuilder("git", "clone", config.getRepositoryURL(), repositoryPath); processBuilder.redirectErrorStream(true); Process process = processBuilder.start(); - int exitCode = process.waitFor(); - if (exitCode != EXIT_SUCCESS) { throw new Exception(); } @@ -99,20 +74,18 @@ public void cloneRemote() { } /** - * This method resets the local repository to commitID. - * Used to initially set commit for clone and additionally to - * advance the local repository as we step through commits - * - * @param commitID if empty or null, defaults to HEAD + * Method to reset repository to a given commit + * + * @param commitID commit id to reset to */ public void resetLocal(String commitID) { validateLocalExists(); - // If an invalid commit is passed simply make no change if (Objects.isNull(commitID) || commitID.isEmpty()) { return; } + // Reset branch to old commit try (Git git = new Git(repository)) { git.reset().setMode(ResetCommand.ResetType.HARD).setRef(commitID).call(); } catch (Exception e) { @@ -120,13 +93,10 @@ public void resetLocal(String commitID) { } LoggerManager.info(() -> "Set repository " + config.getRepoName() + " to " + commitID); - - } /** - * This method validates that the local repository exists or - * reports and exits if it doesn't. + * Method to check that local directory exists */ private void validateLocalExists() { File file = new File(FileUtils.getRepositoryPath(config.getRepoName())); @@ -136,9 +106,9 @@ private void validateLocalExists() { } /** - * Establish a local endpoint for the given repository path. - * - * @return the repository object + * Method to initialize repository from repository name + * + * @return file repository */ public Repository initRepository() { validateLocalExists(); @@ -150,19 +120,18 @@ public Repository initRepository() { repository = new FileRepositoryBuilder().setGitDir(new File(repositoryPath, ".git")).build(); } catch (Exception e) { - Error.reportAndExit(Error.GIT_FAILED,Optional.of(e)); + Error.reportAndExit(Error.GIT_FAILED, Optional.of(e)); } return repository; } - /** - * Get the differences between commitOld and commitNew - * - * @param commitOld the old commit ID - * @param commitNew the new commit ID - * @return the list of differences as DiffEntrys + * Method to get differences between old and new commits + * + * @param commitOld old commit id + * @param commitNew new commit id + * @return list of changes from old commit to new commit */ public List getDifferences(String commitOld, String commitNew) { List returnList = null; @@ -170,6 +139,7 @@ public List getDifferences(String commitOld, String commitNew) { RevWalk revWalk = new RevWalk(repository); try { + // Parse the old and new commits oldCommit = revWalk.parseCommit(repository.resolve(commitOld)); newCommit = revWalk.parseCommit(repository.resolve(commitNew)); } catch (Exception e) { @@ -180,16 +150,24 @@ public List getDifferences(String commitOld, String commitNew) { try (ObjectReader reader = repository.newObjectReader()) { CanonicalTreeParser oldTreeParser = new CanonicalTreeParser(); CanonicalTreeParser newTreeParser = new CanonicalTreeParser(); - oldTreeParser.reset(reader, oldCommit.getTree()); - newTreeParser.reset(reader, newCommit.getTree()); - // Compute differences + // Use tree objects from the commits + oldTreeParser.reset(reader, oldCommit.getTree().getId()); + newTreeParser.reset(reader, newCommit.getTree().getId()); + + // Compute differences between the trees of the two commits try (Git git = new Git(repository)) { - returnList = git.diff() - .setNewTree(newTreeParser) + List rawDiffs = git.diff() .setOldTree(oldTreeParser) + .setNewTree(newTreeParser) .call(); + // Filter out diffs that only contain whitespace or comment changes + RevCommit finalOldCommit = oldCommit; + RevCommit finalNewCommit = newCommit; + returnList = rawDiffs.stream() + .filter(diff -> isCodeChange(diff, repository, finalOldCommit, finalNewCommit)) + .collect(Collectors.toList()); } } catch (Exception e) { Error.reportAndExit(Error.GIT_FAILED, Optional.of(e)); @@ -200,6 +178,86 @@ public List getDifferences(String commitOld, String commitNew) { return returnList; } + /** + * Method to check if a commit difference was a change to the code + * + * @param diff DiffEntry object + * @param repository repository to check + * @param oldCommit old commit id + * @param newCommit new commit id + * + * @return true if difference was a change to the code, false otherwise + */ + private boolean isCodeChange(DiffEntry diff, Repository repository, RevCommit oldCommit, RevCommit newCommit) { + if((!diff.getOldPath().endsWith(".java") && !diff.getNewPath().endsWith(".java"))) { + return true; + } + + // Read the file contents before and after the changes + String oldContent = getContentFromTree(repository, oldCommit.getTree().getId(), diff.getOldPath()); + String newContent = getContentFromTree(repository, newCommit.getTree().getId(), diff.getNewPath()); + + // Remove comments and whitespace from both contents + String oldCode = stripCommentsAndWhitespace(oldContent); + String newCode = stripCommentsAndWhitespace(newContent); + + // If the meaningful code is different, return true + return !oldCode.equals(newCode); + } + + /** + * Get file data from a file tree + * + * @param repository repository to check + * @param treeId id of the tree to check + * @param filePath file to get data from + * @return data from the file, or an empty string if an error occurs or file is not found + */ + private String getContentFromTree(Repository repository, ObjectId treeId, String filePath) { + try (ObjectReader reader = repository.newObjectReader(); + TreeWalk treeWalk = new TreeWalk(repository)) { + + // Add the tree to the tree walker + treeWalk.addTree(treeId); + treeWalk.setRecursive(true); // We want to search recursively + + // Walk through the tree to find the file + while (treeWalk.next()) { + String currentPath = treeWalk.getPathString(); + if (currentPath.equals(filePath)) { + // Ensure we have a blob (file) and not a tree + if (treeWalk.getFileMode(0).getObjectType() == Constants.OBJ_BLOB) { + // Read the file content and return it + byte[] data = reader.open(treeWalk.getObjectId(0)).getBytes(); + return new String(data, StandardCharsets.UTF_8); + } + } + } + + } catch (Exception e) { + // Return an empty string in case of an error + return ""; + } + + // If the file is not found, return an empty string + return ""; + } + + /** + * Remove comments and whitespace from file content + * + * @param content string of all file content + * @return string of file content with whitespace and comments removed + */ + private String stripCommentsAndWhitespace(String content) { + return content.replaceAll("(//.*|/\\*[^*]*\\*+(?:[^/*][^*]*\\*+)*/|\\s+)", ""); + } + + /** + * Get Git log + * + * @return Git log as a list + */ public Iterable getLog() { Iterable returnList = null; @@ -212,26 +270,25 @@ public Iterable getLog() { return returnList; } + /** + * Get head commit for the repository + * + * @return commit id of head commit + */ public String getHeadCommit() { String commitID = ""; try { - // Get the reference to HEAD Ref head = repository.findRef(HEAD_COMMIT); RevWalk walk = new RevWalk(repository); - - // Use RevWalk to parse the commit ObjectId commitId = head.getObjectId(); RevCommit commit = walk.parseCommit(commitId); - commitID = commit.getName(); - + walk.close(); } catch (Exception e) { Error.reportAndExit(Error.GIT_FAILED, Optional.of(e)); } return commitID; } - - } diff --git a/src/main/java/edu/university/ecs/lab/common/services/LoggerManager.java b/src/main/java/edu/university/ecs/lab/common/services/LoggerManager.java index 62ef35c7..7bf5e225 100644 --- a/src/main/java/edu/university/ecs/lab/common/services/LoggerManager.java +++ b/src/main/java/edu/university/ecs/lab/common/services/LoggerManager.java @@ -8,27 +8,56 @@ import java.util.Optional; import java.util.function.Supplier; +/** + * Static functions to manage logger object + */ public class LoggerManager { private static final Logger logger = LogManager.getLogger(LoggerManager.class); + + /** + * Log an info message + * + * @param msgSupplier the message to log + */ public static void info(Supplier msgSupplier) { log(Level.INFO, msgSupplier); } + /** + * Log a warning message + * + * @param msgSupplier the message to log + */ public static void warn(Supplier msgSupplier) { log(Level.WARN, msgSupplier); } + /** + * Log a debug message + * + * @param msgSupplier the message to log + */ public static void debug(Supplier msgSupplier) { log(Level.DEBUG, msgSupplier); } + /** + * Log an error message + * + * @param msgSupplier the message to log + */ public static void error(Supplier msgSupplier, Optional exception) { log(Level.ERROR, msgSupplier); exception.ifPresent(e -> logger.error(e.getMessage(), e)); } - + /** + * Log message + * + * @param level the logging level + * @param msgSupplier the message to log + */ private static void log(Level level, Supplier msgSupplier) { logger.log(level, msgSupplier.get()); } diff --git a/src/main/java/edu/university/ecs/lab/common/utils/FlowUtils.java b/src/main/java/edu/university/ecs/lab/common/utils/FlowUtils.java index b0e3a7ed..578bb16b 100644 --- a/src/main/java/edu/university/ecs/lab/common/utils/FlowUtils.java +++ b/src/main/java/edu/university/ecs/lab/common/utils/FlowUtils.java @@ -85,57 +85,6 @@ public static List buildFlows(MicroserviceSystem microserviceSystem) { return allFlows; } -// public static List buildFlows(Map msModelMap) { -// // 1. get controller name & controller endpoint name -// List flows = generateNewFlows(getAllModelControllers(msModelMap)); -// -// for (Flow flow : flows) { -// // 2. get service method call in controller method -// Optional serviceMethodCall = Optional.ofNullable(findServiceMethodCall(flow)); -// if (serviceMethodCall.isPresent()) { -// flow.setServiceMethodCall(serviceMethodCall.get()); -// // 3. get service field variable in controller class by method call -// Optional serviceField = Optional.ofNullable(findServiceField(flow)); -// if (serviceField.isPresent()) { -// flow.setControllerServiceField(serviceField.get()); -// // 4. get service class -// Optional ServiceClass = Optional.ofNullable(findService(flow)); -// if (ServiceClass.isPresent()) { -// flow.setService(ServiceClass.get()); -// // 5. find service method name -// Optional ServiceMethod = Optional.ofNullable(findServiceMethod(flow)); -// if (ServiceMethod.isPresent()) { -// flow.setServiceMethod(ServiceMethod.get()); -// // 6. find method call in the service -// Optional repositoryMethodCall = -// Optional.ofNullable(findRepositoryMethodCall(flow)); -// if (repositoryMethodCall.isPresent()) { -// flow.setRepositoryMethodCall(repositoryMethodCall.get()); -// // 7. find repository variable -// Optional repositoryField = Optional.ofNullable(findRepositoryField(flow)); -// if (repositoryField.isPresent()) { -// flow.setServiceRepositoryField(repositoryField.get()); -// // 8. find repository class -// Optional repositoryClass = Optional.ofNullable(findRepository(flow)); -// if (repositoryClass.isPresent()) { -// flow.setRepository(repositoryClass.get()); -// // 9. find repository method -// Optional repositoryMethod = -// Optional.ofNullable(findRepositoryMethod(flow)); -// if (repositoryMethod.isPresent()) { -// flow.setRepositoryMethod(repositoryMethod.get()); -// } -// } -// } -// } -// } -// } -// } -// } -// } -// return flows; -// } - /** * This method returns a map of microservices to their controller classes * diff --git a/src/main/java/edu/university/ecs/lab/common/utils/JsonReadWriteUtils.java b/src/main/java/edu/university/ecs/lab/common/utils/JsonReadWriteUtils.java index 96bc7560..26967660 100644 --- a/src/main/java/edu/university/ecs/lab/common/utils/JsonReadWriteUtils.java +++ b/src/main/java/edu/university/ecs/lab/common/utils/JsonReadWriteUtils.java @@ -11,6 +11,9 @@ import edu.university.ecs.lab.common.models.serialization.ProjectFileDeserializer; import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Optional; /** @@ -31,13 +34,15 @@ private JsonReadWriteUtils() { * @param filePath the file path where the JSON should be saved */ public static void writeToJSON(String filePath, T object) { - GsonBuilder gsonBuilder = new GsonBuilder(); - gsonBuilder.setPrettyPrinting(); - Gson gson = gsonBuilder.create(); - try (Writer writer = new BufferedWriter(new FileWriter(filePath))) { - gson.toJson(object, writer); + Gson gson = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); + try { + Path path = Paths.get(filePath); + Files.createDirectories(path.getParent()); + try (Writer writer = Files.newBufferedWriter(path)) { + gson.toJson(object, writer); + } } catch (IOException e) { - Error.reportAndExit(Error.INVALID_JSON_READ, Optional.of(e)); + Error.reportAndExit(Error.INVALID_JSON_WRITE, Optional.of(e)); } } diff --git a/src/main/java/edu/university/ecs/lab/common/utils/SourceToObjectUtils.java b/src/main/java/edu/university/ecs/lab/common/utils/SourceToObjectUtils.java index b879c9a2..5cd68f21 100644 --- a/src/main/java/edu/university/ecs/lab/common/utils/SourceToObjectUtils.java +++ b/src/main/java/edu/university/ecs/lab/common/utils/SourceToObjectUtils.java @@ -5,6 +5,7 @@ import com.github.javaparser.ast.Node; import com.github.javaparser.ast.PackageDeclaration; import com.github.javaparser.ast.body.*; +import com.github.javaparser.ast.body.Parameter; import com.github.javaparser.ast.expr.*; import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName; import com.github.javaparser.resolution.UnsolvedSymbolException; @@ -139,9 +140,9 @@ public static Set parseMethods(List methodDeclaration Set methods = new HashSet<>(); for (MethodDeclaration methodDeclaration : methodDeclarations) { - Set parameters = new HashSet<>(); + Set parameters = new HashSet<>(); for (Parameter parameter : methodDeclaration.getParameters()) { - parameters.add(new Field(parameter.getNameAsString(), packageAndClassName, parameter.getTypeAsString())); + parameters.add(new edu.university.ecs.lab.common.models.ir.Parameter(parameter, packageAndClassName)); } Method method = new Method( @@ -192,8 +193,8 @@ public static Method convertValidEndpoints(MethodDeclaration methodDeclaration, * @param methodDeclarations the list of methodDeclarations to be parsed * @return a set of MethodCall models representing MethodCallExpressions found in the MethodDeclarations */ - public static Set parseMethodCalls(List methodDeclarations) { - Set methodCalls = new HashSet<>(); + public static List parseMethodCalls(List methodDeclarations) { + List methodCalls = new ArrayList<>(); // loop through method calls for (MethodDeclaration methodDeclaration : methodDeclarations) { @@ -231,7 +232,7 @@ public static MethodCall convertValidRestCalls(MethodCallExpr methodCallExpr, Me return methodCall; } - RestCallTemplate restCallTemplate = new RestCallTemplate(methodCallExpr, cu); + RestCallTemplate restCallTemplate = new RestCallTemplate(methodCallExpr,methodCall, cu); if (restCallTemplate.getUrl().isEmpty()) { return methodCall; @@ -316,22 +317,7 @@ private static Set parseAnnotations(Iterable annotat Set annotations = new HashSet<>(); for (AnnotationExpr ae : annotationExprs) { - Annotation annotation; - if (ae.isNormalAnnotationExpr()) { - NormalAnnotationExpr normal = ae.asNormalAnnotationExpr(); - annotation = new Annotation(ae.getNameAsString(), packageAndClassName, normal.getPairs().toString()); - - } else if (ae.isSingleMemberAnnotationExpr()) { - annotation = - new Annotation( - ae.getNameAsString(), - packageAndClassName, - ae.asSingleMemberAnnotationExpr().getMemberValue().toString()); - } else { - annotation = new Annotation(ae.getNameAsString(), packageAndClassName, ""); - } - - annotations.add(annotation); + annotations.add(new Annotation(ae, packageAndClassName)); } return annotations; @@ -393,14 +379,37 @@ private static JClass handleFeignClient(AnnotationExpr requestMapping, Set newMethods = new HashSet<>(); // New rest calls for conversion - Set newRestCalls = new HashSet<>(); + List newRestCalls = new ArrayList<>(); // For each method that is detected as an endpoint convert into a Method + RestCall for(Method method : methods) { if(method instanceof Endpoint) { Endpoint endpoint = (Endpoint) method; newMethods.add(new Method(method.getName(), packageAndClassName, method.getParameters(), method.getReturnType(), method.getAnnotations(), method.getMicroserviceName(), method.getClassName())); - newRestCalls.add(new RestCall(new MethodCall("exchange", packageAndClassName, "RestCallTemplate", "restCallTemplate", method.getName(), "", endpoint.getMicroserviceName(), endpoint.getClassName()), endpoint.getUrl(), endpoint.getHttpMethod())); + + StringBuilder queryParams = new StringBuilder(); + for(edu.university.ecs.lab.common.models.ir.Parameter parameter : method.getParameters()) { + for(Annotation annotation : parameter.getAnnotations()) { + if(annotation.getName().equals("RequestParam")) { + queryParams.append("&"); + if(annotation.getAttributes().containsKey("default")) { + queryParams.append(annotation.getAttributes().get("default")); + } else if(annotation.getAttributes().containsKey("name")) { + queryParams.append(annotation.getAttributes().get("name")); + } else { + queryParams.append(parameter.getName()); + } + + queryParams.append("={?}"); + } + } + } + + if(!queryParams.isEmpty()) { + queryParams.replace(0, 1, "?"); + } + + newRestCalls.add(new RestCall(new MethodCall("exchange", packageAndClassName, "RestCallTemplate", "restCallTemplate", method.getName(), "", endpoint.getMicroserviceName(), endpoint.getClassName()), endpoint.getUrl() + queryParams, endpoint.getHttpMethod())); } else { newMethods.add(method); } @@ -464,7 +473,7 @@ private static JClass handleRepositoryRestResource(AnnotationExpr requestMapping // New methods for conversion Set newEndpoints = new HashSet<>(); // New rest calls for conversion - Set newRestCalls = new HashSet<>(); + List newRestCalls = new ArrayList<>(); // Arbitrary preURL naming scheme if not defined in the annotation String preURL = "/" + className.toLowerCase().replace("repository", "") + "s"; @@ -537,7 +546,7 @@ private static JClass handleRepositoryRestResource(AnnotationExpr requestMapping } private static JClass handleJS(String filePath) { - JClass jClass = new JClass(filePath, filePath, "", ClassRole.FEIGN_CLIENT, new HashSet<>(), new HashSet<>(), new HashSet<>(), new HashSet<>(), new HashSet<>()); + JClass jClass = new JClass(filePath, filePath, "", ClassRole.FEIGN_CLIENT, new HashSet<>(), new HashSet<>(), new HashSet<>(), new ArrayList<>(), new HashSet<>()); try { Set restCalls = new HashSet<>(); // Command to run Node.js script diff --git a/src/main/java/edu/university/ecs/lab/delta/services/DeltaExtractionService.java b/src/main/java/edu/university/ecs/lab/delta/services/DeltaExtractionService.java index 17210f94..1fcb1548 100644 --- a/src/main/java/edu/university/ecs/lab/delta/services/DeltaExtractionService.java +++ b/src/main/java/edu/university/ecs/lab/delta/services/DeltaExtractionService.java @@ -3,11 +3,8 @@ import com.google.gson.JsonObject; import edu.university.ecs.lab.common.config.Config; import edu.university.ecs.lab.common.config.ConfigUtil; -import edu.university.ecs.lab.common.error.Error; import edu.university.ecs.lab.common.models.ir.ConfigFile; import edu.university.ecs.lab.common.models.ir.JClass; -import edu.university.ecs.lab.common.models.ir.Microservice; -import edu.university.ecs.lab.common.models.ir.MicroserviceSystem; import edu.university.ecs.lab.common.services.GitService; import edu.university.ecs.lab.common.services.LoggerManager; import edu.university.ecs.lab.common.utils.FileUtils; @@ -18,13 +15,8 @@ import edu.university.ecs.lab.delta.models.enums.ChangeType; import org.eclipse.jgit.diff.DiffEntry; -import javax.json.Json; import java.io.File; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Iterator; import java.util.List; -import java.util.Optional; /** * Service for extracting the differences between two commits of a repository. @@ -53,42 +45,36 @@ public class DeltaExtractionService { */ private final String commitNew; - /** - * The old IR for validating delta file changes - */ -// private final MicroserviceSystem oldSystem; - /** * System change object that will be returned */ private SystemChange systemChange; /** - * Represents a list of diff entries that are related to pom.xml add or delete + * The type of change that is made */ -// private final List pomDiffs; + private ChangeType changeType; /** - * The type of change that is made + * The path to the output file */ - private ChangeType changeType; + private String outputPath; /** * Constructor for the DeltaExtractionService * * @param configPath path to the config file - * @param oldIRPath path to the oldIR + * @param outputPath output path for file * @param commitOld old commit for comparison * @param commitNew new commit for comparison */ - public DeltaExtractionService(String configPath, String oldIRPath, String commitOld, String commitNew) { + public DeltaExtractionService(String configPath, String outputPath, String commitOld, String commitNew) { this.config = ConfigUtil.readConfig(configPath); this.gitService = new GitService(configPath); this.commitOld = commitOld; this.commitNew = commitNew; -// this.oldSystem = JsonReadWriteUtils.readFromJSON(oldIRPath, MicroserviceSystem.class); -// pomDiffs = new ArrayList<>(); + this.outputPath = outputPath.isEmpty() ? "./Delta.json" : outputPath; } /** @@ -111,6 +97,11 @@ public void generateDelta() { } + /** + * Process differences between commits + * + * @param diffEntries list of differences + */ public void processDelta(List diffEntries) { // Set up a new SystemChangeObject systemChange = new SystemChange(); @@ -152,19 +143,8 @@ public void processDelta(List diffEntries) { } - changeType = ChangeType.fromDiffEntry(entry); - // Special check for pom file manipulations only -// if(path.endsWith("/pom.xml")) { -// // Get some configuration change -// data = configurationChange(entry, oldPath, newPath, path); -// -// if(data == null) { -// continue; -// } -// } else { - switch(changeType) { case ADD: data = add(newPath); @@ -176,19 +156,11 @@ public void processDelta(List diffEntries) { data = delete(); } -// if(data == null) { -// continue; -// } -// } - - systemChange.getChanges().add(new Delta(oldPath, newPath, changeType, data)); } - String filePath = "./output/Delta_" + commitOld.substring(0, 4) + "_" + commitNew.substring(0, 4) + ".json"; - // Output the system changes - JsonReadWriteUtils.writeToJSON(filePath, systemChange); + JsonReadWriteUtils.writeToJSON(outputPath, systemChange); // Report LoggerManager.info(() -> "Delta changes extracted between " + commitOld + " -> " + commitNew); @@ -225,53 +197,6 @@ private JsonObject add(String newPath) { } - /** - * This method parses modified files and handles additional logic related to orphan management - * - * [!] Note: Due to the management of orphans we will do special checks here. - * - * @param path the file path that will be parsed - * @return - */ -// private JsonObject modify(String oldPath) { -// JsonObject jsonObject = null; -// JClass jClass = null; -// -// if(FileUtils.isConfigurationFile(oldPath)) { -// // Special check for modifying a pom that was previously filtered out for being too general -// if(oldPath.endsWith("pom.xml") && oldSystem.findFile(oldPath) == null) { -// return null; -// } -// jsonObject = add(oldPath); -// } else { -// jClass = SourceToObjectUtils.parseClass(new File(FileUtils.gitPathToLocalPath(oldPath, config.getRepoName())), config, ""); -// } -// -// if(jClass != null) { -// jsonObject = jClass.toJsonObject(); -// } -// -// // Similar to add check, but if we couldn't parse and the class exists in old system we must allow it -// if (jsonObject == null && oldSystem.findFile(oldPath) == null) { -// return null; -// // If the class is unparsable and the class exists in the old system we must delete it now -// } else if (jsonObject == null && oldSystem.findFile(oldPath) != null) { -// changeType = ChangeType.DELETE; -// return delete(); -// // If the class is parsable and the class doesn't exist in the old system we must add it now -// } else if (jsonObject != null && oldSystem.findFile(oldPath) == null) { -// changeType = ChangeType.ADD; -// return jsonObject; -// } else if(jsonObject == null) { -// System.err.println("No file found for " + oldPath); -// System.exit(1); -// } -// -// -// -// return jsonObject; -// } - /** * This method returns a blank JsonObject() as there is no data to parse * diff --git a/src/main/java/edu/university/ecs/lab/detection/DetectionService.java b/src/main/java/edu/university/ecs/lab/detection/DetectionService.java index d6b4a725..d17618a5 100644 --- a/src/main/java/edu/university/ecs/lab/detection/DetectionService.java +++ b/src/main/java/edu/university/ecs/lab/detection/DetectionService.java @@ -42,22 +42,22 @@ public class DetectionService { /** * Column labels for violation counts and metrics */ - private static final String[] columnLabels = new String[]{"Commit ID", "Greedy Microservices", "Hub-like Microservices", "Service Chains (MS level)", "Service Chains (Method level)", - "Wrong Cuts", "Cyclic Dependencies (MS level)", "Cyclic Dependencies (Method level)", "Wobbly Service Interactions", "No Healthchecks", - "No API Gateway", "maxAIS", "avgAIS", "stdAIS", "maxADC", "ADCS", "stdADS", "maxACS", "avgACS", "stdACS", "SCF", "SIY", "maxSC", "avgSC", + private static final String[] columnLabels = new String[]{"Commit ID", "Greedy Microservices", "Hub-like Microservices", "Service Chains", + "Wrong Cuts", "Cyclic Dependencies", "Wobbly Service Interactions", "No Healthchecks", + "No API Gateway", "maxAIS", "avgAIS", "stdAIS", "maxADS", "ADCS", "stdADS", "maxACS", "avgACS", "stdACS", "SCF", "SIY", "maxSC", "avgSC", "stdSC", "SCCmodularity", "maxSIDC", "avgSIDC", "stdSIDC", "maxSSIC", "avgSSIC", "stdSSIC", - "maxLOMLC", "avgLOMLC", "stdLOMLC", "AR3 (System)","AR4 (System)", "AR6 (Delta)", "AR20 (System)"}; + "maxLOMLC", "avgLOMLC", "stdLOMLC"}; /** * Count of antipatterns, metrics, and architectural rules */ - private static final int ANTIPATTERNS = 10; + private static final int ANTIPATTERNS = 8; private static final int METRICS = 24; private static final int ARCHRULES = 4; - // private static final String OLD_IR_PATH = "./output/OldIR_"; - private static final String DELTA_PATH = "./output/Delta_"; - // private static final String NEW_IR_PATH = "./output/NewIR_"; + private static String BASE_DELTA_PATH = "/Delta/Delta"; + private static String BASE_IR_PATH = "/IR/IR"; + /** @@ -87,6 +87,9 @@ public DetectionService(String configPath) { // Setup local repo gitService = new GitService(configPath); workbook = new XSSFWorkbook(); + + BASE_DELTA_PATH = "./output/" + config.getRepoName() + BASE_DELTA_PATH; + BASE_IR_PATH = "./output/" + config.getRepoName() + BASE_IR_PATH; } /** @@ -101,7 +104,7 @@ public void runDetection() { // Generate the initial IR irExtractionService = new IRExtractionService(configPath, Optional.of(commits.get(0).toString().split(" ")[1])); String firstCommit = commits.get(0).getName().substring(0, 4); - irExtractionService.generateIR("IR_" + firstCommit + ".json"); + irExtractionService.generateIR(BASE_IR_PATH + "1_" + firstCommit + ".json"); // Setup sheet and headers sheet = workbook.createSheet(config.getSystemName()); @@ -130,27 +133,27 @@ public void runDetection() { // Set the commitID as the first cell value Cell commitIdCell = row.createCell(0); - commitIdCell.setCellValue(commitIdOld.substring(0, 7)); + commitIdCell.setCellValue(commitIdOld); // Read in the old system - String oldSysPath = "./output/IR_" + commitIdOld.substring(0, 4) +".json"; - MicroserviceSystem oldSystem = JsonReadWriteUtils.readFromJSON(oldSysPath, MicroserviceSystem.class); + String oldIRPath = BASE_IR_PATH + (i+1) + "_" + commitIdOld.substring(0, 4) +".json"; + MicroserviceSystem oldSystem = JsonReadWriteUtils.readFromJSON(oldIRPath, MicroserviceSystem.class); // Extract changes from one commit to the other if(i < commits.size() - 1) { String commitIdNew = commits.get(i + 1).toString().split(" ")[1]; - String newSysPath = "./output/IR_" + commitIdNew.substring(0, 4) +".json"; - String deltaPath = DELTA_PATH + commitIdOld.substring(0, 4) + "_" + commitIdNew.substring(0, 4) + ".json"; + String newIRPath = BASE_IR_PATH + (i+2) + "_" + commitIdNew.substring(0, 4) +".json"; + String deltaPath = BASE_DELTA_PATH + (i+1) + "_" + commitIdOld.substring(0, 4) + "_" + commitIdNew.substring(0, 4) + ".json"; - deltaExtractionService = new DeltaExtractionService(configPath, oldSysPath, commitIdOld, commitIdNew); + deltaExtractionService = new DeltaExtractionService(configPath, deltaPath, commitIdOld, commitIdNew); deltaExtractionService.generateDelta(); // Merge Delta changes to old IR to create new IR representing new commit changes - MergeService mergeService = new MergeService(oldSysPath, deltaPath, configPath); + MergeService mergeService = new MergeService(oldIRPath, deltaPath, configPath, newIRPath); mergeService.generateMergeIR(commitIdNew.substring(0, 4)); // Read in the new system and system change - newSystem = JsonReadWriteUtils.readFromJSON(newSysPath, MicroserviceSystem.class); + newSystem = JsonReadWriteUtils.readFromJSON(newIRPath, MicroserviceSystem.class); systemChange = JsonReadWriteUtils.readFromJSON(deltaPath, SystemChange.class); } @@ -162,22 +165,20 @@ public void runDetection() { // We can detect/update if there are >= 1 microservices if (Objects.nonNull(oldSystem.getMicroservices()) && !oldSystem.getMicroservices().isEmpty()) { detectAntipatterns(oldSystem, antipatterns); - detectMetrics(oldSystem, metrics, commitIdOld.substring(0, 4)); + detectMetrics(oldSystem, metrics, oldIRPath); updateAntiPatterns(currIndex, antipatterns); updateMetrics(currIndex, metrics); - - // For simplicity we will skip rules on the last iteration since there is no newSystem - if(i < commits.size() - 1) { - arDetectionService = new ARDetectionService(systemChange, oldSystem, newSystem); - rules = arDetectionService.scanUseCases(); - - updateRules(nextIndex, rules); - } else { - continue; - } } + // For simplicity we will skip rules on the last iteration since there is no newSystem +// if(i < commits.size() - 1) { +// arDetectionService = new ARDetectionService(systemChange, oldSystem, newSystem); +// rules = arDetectionService.scanUseCases(); +// +// updateRules(nextIndex, rules); +// } + // After completing this iteration, we can replace oldIR with newIR // try { // Files.move(Paths.get(NEW_IR_PATH), Paths.get(OLD_IR_PATH), StandardCopyOption.REPLACE_EXISTING); @@ -188,9 +189,9 @@ public void runDetection() { } // At the end we write the workbook to file - try (FileOutputStream fileOut = new FileOutputStream(String.format("./output/AntiPatterns_%s.xlsx", config.getSystemName()))) { + try (FileOutputStream fileOut = new FileOutputStream(String.format("./output/%s/output-%s.xlsx",config.getRepoName(), config.getSystemName()))) { workbook.write(fileOut); - System.out.printf("Excel file created: AntiPatterns_%s.xlsx%n", config.getSystemName()); +// System.out.printf("Excel file created: AntiPatterns_%s.xlsx%n", config.getSystemName()); workbook.close(); } catch (Exception e) { e.printStackTrace(); @@ -254,18 +255,18 @@ private void detectAntipatterns(MicroserviceSystem microserviceSystem, Map metrics, String commitID) { + private void detectMetrics(MicroserviceSystem microserviceSystem, Map metrics, String oldIRPath) { // Create SDG ServiceDependencyGraph sdg = new ServiceDependencyGraph(microserviceSystem); @@ -294,7 +295,7 @@ private void detectMetrics(MicroserviceSystem microserviceSystem, Map currARs) { arcrules_counts[1]++; } else if (archRule instanceof AR6) { arcrules_counts[2]++; - } else if (archRule instanceof AR20) { + } else if (archRule instanceof AR7) { arcrules_counts[3]++; } } diff --git a/src/main/java/edu/university/ecs/lab/detection/ExcelOutputRunner.java b/src/main/java/edu/university/ecs/lab/detection/ExcelOutputRunner.java index 0a3f76e7..66a22b7b 100644 --- a/src/main/java/edu/university/ecs/lab/detection/ExcelOutputRunner.java +++ b/src/main/java/edu/university/ecs/lab/detection/ExcelOutputRunner.java @@ -1,15 +1,27 @@ package edu.university.ecs.lab.detection; +import edu.university.ecs.lab.common.error.Error; + +import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; +import java.util.Optional; /** * Runner class to execute detection service */ public class ExcelOutputRunner { - public static void main(String[] args) throws IOException { - String configPath = "./valid_configs/java-microservice.json"; + String configPath = "./config.json"; + try { + File conifgFile = new File(configPath); + if (!conifgFile.exists()) { + throw new FileNotFoundException(); + } + } catch (Exception e) { + Error.reportAndExit(Error.MISSING_CONFIG, Optional.of(e)); + } DetectionService detectionService = new DetectionService(configPath); detectionService.runDetection(); } diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/AntiPattern.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/AbstractAntiPattern.java similarity index 91% rename from src/main/java/edu/university/ecs/lab/detection/antipatterns/models/AntiPattern.java rename to src/main/java/edu/university/ecs/lab/detection/antipatterns/models/AbstractAntiPattern.java index 9d5a27f0..424740e3 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/AntiPattern.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/AbstractAntiPattern.java @@ -7,7 +7,7 @@ * Abstract implementation of an Antipattern should be the parent * of all system Antipatterns */ -public abstract class AntiPattern implements JsonSerializable { +public abstract class AbstractAntiPattern implements JsonSerializable { protected abstract String getName(); protected abstract String getDescription(); protected abstract JsonObject getMetaData(); diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/CyclicDependency.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/CyclicDependency.java index 8324739b..6c0c76c5 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/CyclicDependency.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/CyclicDependency.java @@ -13,7 +13,7 @@ * Represents a list of one cycle of Cyclic Dependency Anti-pattern detected */ @Data -public class CyclicDependency extends AntiPattern{ +public class CyclicDependency extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/GreedyMicroservice.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/GreedyMicroservice.java index 565a55b6..106be674 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/GreedyMicroservice.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/GreedyMicroservice.java @@ -11,7 +11,7 @@ * Represents a collection of microservices identified as greedy. */ @Data -public class GreedyMicroservice extends AntiPattern { +public class GreedyMicroservice extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/HubLikeMicroservice.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/HubLikeMicroservice.java index 8affba78..92d5b404 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/HubLikeMicroservice.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/HubLikeMicroservice.java @@ -10,7 +10,7 @@ * Represents a collection of microservices identified as hub-like. */ @Data -public class HubLikeMicroservice extends AntiPattern { +public class HubLikeMicroservice extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoApiGateway.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoApiGateway.java index 1baae595..6b401240 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoApiGateway.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoApiGateway.java @@ -8,7 +8,7 @@ * Represents the "No API-Gateway" anti-pattern */ @Data -public class NoApiGateway extends AntiPattern{ +public class NoApiGateway extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoHealthcheck.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoHealthcheck.java index 4604e6da..60956a05 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoHealthcheck.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/NoHealthcheck.java @@ -11,7 +11,7 @@ * Represents the "No Health Check" anti-pattern */ @Data -public class NoHealthcheck extends AntiPattern{ +public class NoHealthcheck extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/ServiceChain.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/ServiceChain.java index 8817b7ba..7295b973 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/ServiceChain.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/ServiceChain.java @@ -11,7 +11,7 @@ * Represents a service chain, which is a sequence of services in a network graph. */ @Data -public class ServiceChain extends AntiPattern{ +public class ServiceChain extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WobblyServiceInteraction.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WobblyServiceInteraction.java index a00e2c31..dbdc7c15 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WobblyServiceInteraction.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WobblyServiceInteraction.java @@ -11,7 +11,7 @@ * Represents a wobbly service interaction. */ @Data -public class WobblyServiceInteraction extends AntiPattern { +public class WobblyServiceInteraction extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WrongCuts.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WrongCuts.java index 880bd60a..e82819c0 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WrongCuts.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/models/WrongCuts.java @@ -11,7 +11,7 @@ * Represents a cluster of wrongly interconnected services (Wrong Cuts) detected in a microservice network graph. */ @Data -public class WrongCuts extends AntiPattern{ +public class WrongCuts extends AbstractAntiPattern { /** * Anti-pattern name */ diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/CyclicDependencyMSLevelService.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/CyclicDependencyMSLevelService.java index 7793c783..19a0cbd7 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/CyclicDependencyMSLevelService.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/CyclicDependencyMSLevelService.java @@ -11,7 +11,7 @@ */ public class CyclicDependencyMSLevelService { - private List> allCycles = new ArrayList<>(); // + private List> allCycles = new ArrayList<>(); private Set visited = new HashSet<>(); private Set recStack = new HashSet<>(); private Map parentMap = new HashMap<>(); @@ -73,15 +73,18 @@ private List reconstructCyclePath(Microservice startNode, Microservice c List fullCyclePath = new ArrayList<>(); Microservice node = currentNode; - // Iterate through each node in parent map until startNode is reached, adding each to + // Iterate through each node in parent map until startNode is reached, adding each to // the cycle path - fullCyclePath.add(startNode.getName()); while (node != null && !node.equals(startNode)) { fullCyclePath.add(node.getName()); node = parentMap.get(node); } + + // Only add startNode at the end to complete the cycle fullCyclePath.add(startNode.getName()); + Collections.reverse(fullCyclePath); // Reverse the path to get correct order + return fullCyclePath; } } diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/ServiceChainMSLevelService.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/ServiceChainMSLevelService.java index c5cb9af1..78525fa0 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/ServiceChainMSLevelService.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/ServiceChainMSLevelService.java @@ -7,31 +7,31 @@ import java.util.*; /** - * Service class for detecting and managing service chains in a network graph. + * ServiceChainMSLevelService detects service chains in a microservice architecture using DFS. */ public class ServiceChainMSLevelService { /** - * Length of the chain to consider an anti-pattern. + * Default chain length to consider as an anti-pattern. */ protected static final int DEFAULT_CHAIN_LENGTH = 3; private final int CHAIN_LENGTH; - private Set visited = new HashSet<>(); private List> allChains = new ArrayList<>(); private ServiceDependencyGraph graph = null; private List currentPath = new ArrayList<>(); + private boolean hasCycle; // Flag to indicate if a cycle has been detected in the current path /** - * Constructs the service with a default chain length of 3. + * Constructs the service with the default chain length of 3. */ public ServiceChainMSLevelService() { this.CHAIN_LENGTH = DEFAULT_CHAIN_LENGTH; } - + /** * Constructs the service with a specified chain length. - * - * @param CHAIN_LENGTH the length of the chain to consider an anti-pattern + * + * @param CHAIN_LENGTH the length of the chain to consider an anti-pattern. */ public ServiceChainMSLevelService(int CHAIN_LENGTH) { this.CHAIN_LENGTH = CHAIN_LENGTH; @@ -40,37 +40,57 @@ public ServiceChainMSLevelService(int CHAIN_LENGTH) { /** * Retrieves all service chains from the given network graph. * - * @param graph the network graph to analyze - * @return a ServiceChain object representing all detected service chains + * @param graph the network graph to analyze. + * @return a ServiceChain object representing all detected service chains. */ public ServiceChain getServiceChains(ServiceDependencyGraph graph) { allChains = new ArrayList<>(); this.graph = graph; - visited = new HashSet<>(); - graph.vertexSet().stream().filter(node -> !visited.contains(node)).forEach(node -> { - this.currentPath = new ArrayList<>(); - dfs(node); - }); + // Traverse every node in the graph, starting a DFS from each node + for (Microservice node : graph.vertexSet()) { + currentPath = new ArrayList<>(); + hasCycle = false; // Reset the cycle detection flag for each DFS call + dfs(node, new HashSet<>()); // Pass a new empty set for each DFS path to detect cycles + } + // Return the detected service chains wrapped in a ServiceChain object. return new ServiceChain(allChains); } /** - * Depth-first search (DFS) to explore and detect service chains starting from currentNode. + * Depth-first search (DFS) to explore and detect service chains starting from the currentNode. + * Detects cycles by ensuring that the current node is not already in the current DFS path. * - * @param currentNode the current node being visited + * @param currentNode the current node being visited in the DFS. + * @param pathVisited a set to track nodes visited in the current DFS path (for cycle detection). */ - private void dfs(Microservice currentNode) { + private void dfs(Microservice currentNode, Set pathVisited) { + // If the current node is in the current DFS path, there's a cycle + if (pathVisited.contains(currentNode)) { + hasCycle = true; // Mark that a cycle was detected in this path + return; // Stop processing this path due to a cycle + } + + // Add the current node to the current path and mark it visited in the DFS path currentPath.add(currentNode.getName()); - visited.add(currentNode); + pathVisited.add(currentNode); - graph.getAdjacency(currentNode).stream().filter(neighbor -> !visited.contains(neighbor)).forEach(this::dfs); + // Recur for all adjacent nodes (neighbors) of the current node + for (Microservice neighbor : graph.getAdjacency(currentNode)) { + dfs(neighbor, pathVisited); // Recursive DFS call for all neighbors + } - if (currentPath.size() >= CHAIN_LENGTH) { - allChains.add(new ArrayList<>(currentPath)); + // If the current path reaches or exceeds the chain length and no cycle was detected, add it to the list of service chains + if (currentPath.size() >= CHAIN_LENGTH && !hasCycle) { + allChains.add(new ArrayList<>(currentPath)); // Add a copy of the current path } + // Backtrack: remove the current node from the path and the DFS path set currentPath.remove(currentPath.size() - 1); + pathVisited.remove(currentNode); } } + + + diff --git a/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/WobblyServiceInteractionService.java b/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/WobblyServiceInteractionService.java index 2e30b9f3..97762cfe 100644 --- a/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/WobblyServiceInteractionService.java +++ b/src/main/java/edu/university/ecs/lab/detection/antipatterns/services/WobblyServiceInteractionService.java @@ -33,6 +33,7 @@ public WobblyServiceInteraction findWobblyServiceInteractions(MicroserviceSystem boolean hasRateLimiter = false; boolean hasRetry = false; boolean hasBulkhead = false; + boolean passes = false; // Check annotations at the method level for (Method method : jClass.getMethods()) { diff --git a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR3.java b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR3.java index 078090fa..3ae09342 100644 --- a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR3.java +++ b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR3.java @@ -128,7 +128,7 @@ private static boolean findMatch(RestCall restCall, MicroserviceSystem newSystem public static List scan2(MicroserviceSystem oldSystem, MicroserviceSystem newSystem) { List archRules = new ArrayList<>(); - List allRestCalls = newSystem.getMicroservices().stream().flatMap(microservice -> microservice.getServices().stream()).flatMap(jClass -> jClass.getRestCalls().stream()).collect(Collectors.toList()); + List allRestCalls = newSystem.getMicroservices().stream().flatMap(microservice -> microservice.getRestCalls().stream()).collect(Collectors.toList()); // For each restCall if we don't find a match in the new System diff --git a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR4.java b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR4.java index de3c892f..778b677a 100644 --- a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR4.java +++ b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR4.java @@ -16,7 +16,7 @@ import lombok.Data; /** - * Architectural Rule 4 Class: Floating endpoint due to last call removal + * Architectural Rule 4 Class: Floating endpoint */ @Data public class AR4 extends AbstractAR { @@ -25,8 +25,8 @@ public class AR4 extends AbstractAR { * Architectural rule 4 details */ protected static final String TYPE = "Architectural Rule 4"; - protected static final String NAME = "Floating endpoint due to last call removal"; - protected static final String DESC = "Any rest calls referencing an endpoint are now gone. This endpoint is now unused by any other microservice"; + protected static final String NAME = "Floating endpoint"; + protected static final String DESC = "No rest calls reference this endpoint. This endpoint is now unused by any other microservice"; private String oldCommitID; private String newCommitID; @@ -73,7 +73,7 @@ public static List scan(Delta delta, MicroserviceSystem oldSystem, Microser // Get endpoints that do not have any calls Set uncalledEndpoints = getEndpointsWithNoCalls(newSystem); - Set restCalls = new HashSet<>(); + List restCalls = new ArrayList<>(); if (delta.getChangeType().equals(ChangeType.MODIFY)) { restCalls = getRemovedRestCalls(delta, jClass); @@ -123,8 +123,8 @@ public static List scan(Delta delta, MicroserviceSystem oldSystem, Microser * @param oldClass the delta changed class from the oldSystem * @return a set of rest calls */ - private static Set getRemovedRestCalls(Delta delta, JClass oldClass){ - Set removedRestCalls = new HashSet<>(); + private static List getRemovedRestCalls(Delta delta, JClass oldClass){ + List removedRestCalls = new ArrayList<>(); // For the restCalls in delta for(RestCall modifiedRestCall: delta.getClassChange().getRestCalls()){ @@ -194,7 +194,7 @@ public static List scan2(MicroserviceSystem oldSystem, MicroserviceSystem n // If we are not removing or modifying a service // Get endpoints that do not have any calls - Set allEndpoints = newSystem.getMicroservices().stream().flatMap(microservice -> microservice.getControllers().stream()).flatMap(jClass -> jClass.getEndpoints().stream()).collect(Collectors.toSet()); + Set allEndpoints = newSystem.getMicroservices().stream().flatMap(microservice -> microservice.getEndpoints().stream()).collect(Collectors.toSet()); for(Endpoint endpoint: allEndpoints){ @@ -222,12 +222,11 @@ public static List scan2(MicroserviceSystem oldSystem, MicroserviceSystem n */ private static boolean findMatch(Endpoint endpoint, MicroserviceSystem newSystem) { for (Microservice microservice : newSystem.getMicroservices()) { - for (JClass service : microservice.getServices()) { - for (RestCall restcall : service.getRestCalls()) { - if (RestCall.matchEndpoint(restcall, endpoint)) { - return true; - } + for (RestCall restcall : microservice.getRestCalls()) { + if (RestCall.matchEndpoint(restcall, endpoint)) { + return true; } + } } return false; diff --git a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR6.java b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR6.java index 51790cbc..3b5aca78 100644 --- a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR6.java +++ b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR6.java @@ -9,11 +9,9 @@ import edu.university.ecs.lab.delta.models.Delta; import edu.university.ecs.lab.delta.models.enums.ChangeType; import lombok.Data; +import org.apache.poi.ss.formula.functions.T; -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.stream.Collectors; /** @@ -78,21 +76,21 @@ public static List scan(Delta delta, MicroserviceSystem oldSystem, Microser return new ArrayList<>(); } + List uniqueMethodCalls = getUniqueMethodCalls(jClass.getMethodCalls(), delta.getClassChange().getMethodCalls()); + List archRules = new ArrayList<>(); - Set affectedMethods = delta.getClassChange().getMethods(); - Method removeMethod = null; // For each methodCall added - for(MethodCall methodCall : getNewMethodCalls(jClass, delta.getClassChange())) { + for(Method method : delta.getClassChange().getMethods()) { outer: { - for (Method method : affectedMethods) { + + for (MethodCall methodCall : uniqueMethodCalls) { // If the called object is the same as the return type of the same method // TODO This will report the first instance // TODO This currently does not check if the affected method is called by an endpoint and actually used, flows needed if (method.getReturnType().equals(methodCall.getObjectType()) && method.getName().equals(methodCall.getCalledFrom())) { - removeMethod = method; AR6 archRule6 = new AR6(); JsonObject jsonObject = new JsonObject(); @@ -110,23 +108,60 @@ public static List scan(Delta delta, MicroserviceSystem oldSystem, Microser } } - if(Objects.nonNull(removeMethod)) { - affectedMethods.remove(removeMethod); - removeMethod = null; - } } return archRules; } - /** - * This method gets new method calls not present in oldClass - * - * @param oldClass old commit class - * @param newClass delta class change - * @return list of new method calls (not present in old class) - */ - private static Set getNewMethodCalls(JClass oldClass, JClass newClass) { - return newClass.getMethodCalls().stream().filter(methodCall -> !oldClass.getMethodCalls().contains(methodCall)).collect(Collectors.toSet()); + // Define a custom comparator for the MethodCall class + private static Comparator methodCallComparator = Comparator + .comparing(MethodCall::getName) + .thenComparing(MethodCall::getParameterContents) + .thenComparing(MethodCall::getObjectType) + .thenComparing(MethodCall::getCalledFrom); + + public static List getUniqueMethodCalls(List oldMethodCalls, List newMethodCalls) { + // Using TreeSet with a custom comparator to enforce uniqueness based on custom logic + List final1 = new ArrayList<>(); + + + boolean foundMatch = false; + for(MethodCall oldmethodCall : oldMethodCalls) { + outer: + { + for (MethodCall newMethodCall : newMethodCalls) { + if (methodCallComparator.compare(oldmethodCall, newMethodCall) == 0) { + foundMatch = true; + break outer; + } + } + + if(!foundMatch) { + final1.add(oldmethodCall); + } + foundMatch = false; + } + } + + for(MethodCall newMethodCall : newMethodCalls) { + outer: + { + for (MethodCall oldmethodCall : oldMethodCalls) { + if (methodCallComparator.compare(oldmethodCall, newMethodCall) == 0) { + foundMatch = true; + break outer; + } + } + + if(!foundMatch) { + final1.add(newMethodCall); + } + foundMatch = false; + } + } + + + return final1; } + } diff --git a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR7.java b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR7.java index 0ad78d50..5ab6ddee 100644 --- a/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR7.java +++ b/src/main/java/edu/university/ecs/lab/detection/architecture/models/AR7.java @@ -1,7 +1,10 @@ package edu.university.ecs.lab.detection.architecture.models; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; import com.google.gson.JsonObject; @@ -63,32 +66,68 @@ public String getType() { public static List scan(Delta delta, MicroserviceSystem oldSystem, MicroserviceSystem newSystem) { List useCases = new ArrayList<>(); - JClass oldClass = oldSystem.findClass(delta.getOldPath()); + JClass jClass = oldSystem.findClass(delta.getOldPath()); + if(jClass == null) { + return new ArrayList<>(); + } // Return empty list if it isn't modify or not a repository - if (!delta.getChangeType().equals(ChangeType.MODIFY) || !oldClass.getClassRole().equals(ClassRole.REPOSITORY)) { + if (!delta.getChangeType().equals(ChangeType.MODIFY) || !jClass.getClassRole().equals(ClassRole.REPOSITORY)) { return useCases; } - for (Method methodOld : oldClass.getMethods()) { - for (Method methodNew : delta.getClassChange().getMethods()) { - // Match old and new Methods - if (methodOld.getID().equals(methodNew.getID()) && !methodOld.equals(methodNew)) { - // Any annotations that don't equal their counterpart we can add to metadata - for (Annotation annotationOld : methodOld.getAnnotations()) { - for (Annotation annotationNew : methodNew.getAnnotations()) { - // Annotation names match but not the contents - if (annotationNew.getName().equals(annotationOld.getName()) && !annotationNew.getContents().equals(annotationOld.getContents())) { - AR7 useCase7 = new AR7(); - JsonObject jsonObject = new JsonObject(); - jsonObject.isJsonNull(); - jsonObject.addProperty("OldMethodDeclaration", methodOld.getID()); - jsonObject.addProperty("NewMethodDeclaration", methodNew.getID()); - useCase7.setOldCommitID(oldSystem.getCommitID()); - useCase7.setNewCommitID(newSystem.getCommitID()); - - useCase7.setMetaData(jsonObject); - useCases.add(useCase7); + + for (Method methodOld : jClass.getMethods()) { + outer: + { + for (Method methodNew : delta.getClassChange().getMethods()) { + // Match old and new Methods + if (methodOld.getID().equals(methodNew.getID())) { + // Flag any added, removed + Set oldAnnotations = methodOld.getAnnotations().stream() + .map(Annotation::getName) + .collect(Collectors.toSet()); + Set newAnnotations = methodNew.getAnnotations().stream() + .map(Annotation::getName) + .collect(Collectors.toSet()); + + // Check for added or removed annotations + Set addedAnnotations = new HashSet<>(newAnnotations); + addedAnnotations.removeAll(oldAnnotations); + + Set removedAnnotations = new HashSet<>(oldAnnotations); + removedAnnotations.removeAll(newAnnotations); + + if (!addedAnnotations.isEmpty() || !removedAnnotations.isEmpty()) { + AR7 useCase7 = new AR7(); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("OldMethodDeclaration", methodOld.getID()); + jsonObject.addProperty("NewMethodDeclaration", methodNew.getID()); + jsonObject.addProperty("AddedAnnotations", addedAnnotations.toString()); + jsonObject.addProperty("RemovedAnnotations", removedAnnotations.toString()); + useCase7.setOldCommitID(oldSystem.getCommitID()); + useCase7.setNewCommitID(newSystem.getCommitID()); + useCase7.setMetaData(jsonObject); + useCases.add(useCase7); + break outer; + } + + // Check for modified annotations (same name but different contents) + for (Annotation annotationOld : methodOld.getAnnotations()) { + for (Annotation annotationNew : methodNew.getAnnotations()) { + if (annotationNew.getName().equals(annotationOld.getName()) + && !annotationNew.getContents().equals(annotationOld.getContents())) { + AR7 useCase7 = new AR7(); + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("OldMethodDeclaration", methodOld.getID()); + jsonObject.addProperty("NewMethodDeclaration", methodNew.getID()); + jsonObject.addProperty("ModifiedAnnotation", annotationNew.getName()); + useCase7.setOldCommitID(oldSystem.getCommitID()); + useCase7.setNewCommitID(newSystem.getCommitID()); + useCase7.setMetaData(jsonObject); + useCases.add(useCase7); + break outer; + } } } } diff --git a/src/main/java/edu/university/ecs/lab/detection/architecture/services/ARDetectionService.java b/src/main/java/edu/university/ecs/lab/detection/architecture/services/ARDetectionService.java index b4179226..4b385b7b 100644 --- a/src/main/java/edu/university/ecs/lab/detection/architecture/services/ARDetectionService.java +++ b/src/main/java/edu/university/ecs/lab/detection/architecture/services/ARDetectionService.java @@ -82,10 +82,10 @@ public List scanDeltaUC() { } - // List useCase7List = UseCase7.scan(d, microserviceSystemOld, microserviceSystemNew); - // if(!useCase7List.isEmpty()){ - // useCases.addAll(useCase7List); - // } + List useCase7List = AR7.scan(d, microserviceSystemOld, microserviceSystemNew); + if(!useCase7List.isEmpty()){ + useCases.addAll(useCase7List); + } } return useCases; @@ -109,10 +109,10 @@ public List scanSystemUC() { useCases.addAll(useCase4List); } - List useCase20List = AR20.scan(microserviceSystemOld, microserviceSystemNew); - if(!useCase20List.isEmpty()){ - useCases.addAll(useCase20List); - } +// List useCase7List = AR7.scan(demicroserviceSystemOld, microserviceSystemNew); +// if(!useCase7List.isEmpty()){ +// useCases.addAll(useCase7List); +// } // List useCase21List = UseCase21.scan(microserviceSystemOld, microserviceSystemNew); // if(!useCase21List.isEmpty()){ diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/RunCohesionMetrics.java b/src/main/java/edu/university/ecs/lab/detection/metrics/RunCohesionMetrics.java index 7ab67811..d69feedf 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/RunCohesionMetrics.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/RunCohesionMetrics.java @@ -37,41 +37,30 @@ public static MetricResultCalculation calculateCohesionMetrics(String IRPath) { for (Microservice microservice : microserviceSystem.getMicroservices()) { IServiceDescriptor serviceDescriptor = new ServiceDescriptor(); -// JSONObject microservice = microservices.getJSONObject(i); -// String serviceName = microservice.getString("name"); -// JSONArray controllers = microservice.getJSONArray("controllers"); serviceDescriptor.setServiceName(microservice.getName()); for (JClass controller : microservice.getControllers()) { -// JSONObject controller = controllers.getJSONObject(j); -// JSONArray methods = controller.getJSONArray("methods"); List operations = new ArrayList<>(); for (Method method : controller.getMethods()) { -// JSONObject method = methods.getJSONObject(k); Operation operation = new Operation(); String operationName = microservice.getName() + "::" + method.getName(); operation.setResponseType(method.getReturnType()); operation.setName(operationName); -// JSONArray parameters = method.getJSONArray("parameters"); - List paramList = new ArrayList<>(); - for (Field field : method.getParameters()) { -// JSONObject parameter = parameters.getJSONObject(l); - Parameter param = new Parameter(); - param.setName(field.getName()); - param.setType(field.getType()); - paramList.add(param); + List paramList = new ArrayList<>(); + for (edu.university.ecs.lab.common.models.ir.Parameter field : method.getParameters()) { +// Parameter param = new Parameter(); +// param.setName(field.getName()); +// param.setType(field.getType()); + paramList.add(field.getType()); } operation.setParamList(paramList); List usingTypes = new ArrayList<>(); - // Assume annotations can imply using types -// JSONArray annotations = method.getJSONArray("annotations"); for (Annotation annotation : method.getAnnotations()) { -// JSONObject annotation = annotations.getJSONObject(m); usingTypes.add(annotation.getName() + " - " + annotation.getContents()); } operation.setUsingTypesList(usingTypes); diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/models/DegreeCoupling.java b/src/main/java/edu/university/ecs/lab/detection/metrics/models/DegreeCoupling.java index 395412f6..ed1e6039 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/models/DegreeCoupling.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/models/DegreeCoupling.java @@ -98,7 +98,7 @@ public DegreeCoupling(ServiceDependencyGraph graph){ // Service coupling factor (graph density) Map> adjacency = graph.getAdjacency(); double E = adjacency.values().stream().map(Set::size).mapToDouble(Integer::doubleValue).sum(); - long N = AIS.size(); + long N = graph.vertexSet().size(); SCF = E/(N*(N-1)); // Service Interdependence in the System diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/models/Operation.java b/src/main/java/edu/university/ecs/lab/detection/metrics/models/Operation.java index 86dc2554..9d277146 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/models/Operation.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/models/Operation.java @@ -9,7 +9,7 @@ public class Operation { private String path; private String name; - private List paramList; + private List paramList; private List usingTypesList; private String responseType; @@ -37,11 +37,11 @@ public void setName(String name) { this.name = name; } - public List getParamList() { + public List getParamList() { return paramList; } - public void setParamList(List paramList) { + public void setParamList(List paramList) { this.paramList = paramList; } diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/models/StructuralCoupling.java b/src/main/java/edu/university/ecs/lab/detection/metrics/models/StructuralCoupling.java index c880d2a5..82f9c67c 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/models/StructuralCoupling.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/models/StructuralCoupling.java @@ -46,7 +46,7 @@ public class StructuralCoupling { */ private final double stdGWF; /** - * Structural Coupling(s1, s2) = 1 - 1/degree(s1, s2) - LWF(s1, s2)*GWF(s1, s2) + * Structural Coupling(s1, s2) = 1 - 1/degree(s1, s2) - LWF(s1, s2) * GWF(s1, s2) */ private final Map, Double> SC; /** @@ -81,11 +81,21 @@ public StructuralCoupling(ServiceDependencyGraph graph) { graph.getAllEdges(vertexA, vertexB).stream().mapToDouble(graph::getEdgeWeight).sum()); in_degree.put(pair, graph.getAllEdges(vertexB, vertexA).stream().mapToDouble(graph::getEdgeWeight).sum()); + } + } + } + + for (Microservice vertexA: graph.vertexSet()) { + for (Microservice vertexB: graph.vertexSet()) { + List pair = Arrays.asList(vertexA.getName(), vertexB.getName()); + if (!vertexA.equals(vertexB) && graph.containsEdge(vertexA, vertexB)) { degree.put(pair, in_degree.get(pair)+out_degree.get(pair)); LWF.put(pair, (1+out_degree.get(pair))/(1+degree.get(pair))); } } } + + double max_degree; try { max_degree = degree.values().stream().max(Comparator.comparingDouble(Double::doubleValue)). @@ -104,7 +114,7 @@ public StructuralCoupling(ServiceDependencyGraph graph) { } for (List pair: LWF.keySet()) { GWF.put(pair, degree.get(pair)/max_degree); - SC.put(pair, 1-1/degree.get(pair)-LWF.get(pair)*GWF.get(pair)); + SC.put(pair, (1-(1/degree.get(pair)))-LWF.get(pair)*GWF.get(pair)); } // Amount of actually connected pairs diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/services/LackOfMessageLevelCohesion.java b/src/main/java/edu/university/ecs/lab/detection/metrics/services/LackOfMessageLevelCohesion.java index 9abefd53..662b4740 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/services/LackOfMessageLevelCohesion.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/services/LackOfMessageLevelCohesion.java @@ -86,8 +86,8 @@ public Double inputDataSimilarity(Operation firstOperation, Operation secondOper HashSet unionOfProperties = new HashSet<>(); // Get parameter names from each operation - List firstOperationParameterNames = Parameter.getParameterNames(firstOperation.getParamList()); - List secondOperationParameterNames = Parameter.getParameterNames(secondOperation.getParamList()); + List firstOperationParameterNames = firstOperation.getParamList(); + List secondOperationParameterNames = secondOperation.getParamList(); // Join properties of both operations and get total number of properties unionOfProperties.addAll(firstOperationParameterNames); diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/services/ServiceInterfaceDataCohesion.java b/src/main/java/edu/university/ecs/lab/detection/metrics/services/ServiceInterfaceDataCohesion.java index 83877427..faa583c5 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/services/ServiceInterfaceDataCohesion.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/services/ServiceInterfaceDataCohesion.java @@ -50,7 +50,6 @@ public void evaluate() { long totalOfParamsType = serviceDescriptor.getServiceOperations() .stream() .map(o -> o.getParamList()) - .map(o -> Parameter.getParameterTypes(o)) .filter(Objects::nonNull) .flatMap(types -> types.stream()) .distinct() @@ -70,8 +69,8 @@ public void evaluate() { } private List commonParameterTypes(Operation operation1, Operation operation2) { - List typesIntoFirstOperation = Parameter.getParameterTypes(operation1.getParamList()); - List typesIntoSecondOperation = Parameter.getParameterTypes(operation2.getParamList()); + List typesIntoFirstOperation = operation1.getParamList(); + List typesIntoSecondOperation = operation2.getParamList(); return typesIntoFirstOperation.stream() .filter(typesIntoSecondOperation::contains).collect(Collectors.toList()); diff --git a/src/main/java/edu/university/ecs/lab/detection/metrics/services/StrictServiceImplementationCohesion.java b/src/main/java/edu/university/ecs/lab/detection/metrics/services/StrictServiceImplementationCohesion.java index ba1f5d7e..4bfea4d9 100644 --- a/src/main/java/edu/university/ecs/lab/detection/metrics/services/StrictServiceImplementationCohesion.java +++ b/src/main/java/edu/university/ecs/lab/detection/metrics/services/StrictServiceImplementationCohesion.java @@ -70,9 +70,7 @@ public void evaluate() { }).flatMap(types -> types.stream()) .collect(Collectors.toSet()).size(); - // multiplica por dois porque é o conjunto de pares, então tanto a ida como a - // volta - // ex: (Service::A-Service::B e Service::B-Service::A) contam. + this.getResult().setMetricValue(intersectTypesSize * 2.0 / (uniqueUsingTypes.size() * numberOfOperations)); } diff --git a/src/main/java/edu/university/ecs/lab/intermediate/create/services/IRExtractionService.java b/src/main/java/edu/university/ecs/lab/intermediate/create/services/IRExtractionService.java index fd5d85e5..cfd4488b 100644 --- a/src/main/java/edu/university/ecs/lab/intermediate/create/services/IRExtractionService.java +++ b/src/main/java/edu/university/ecs/lab/intermediate/create/services/IRExtractionService.java @@ -189,7 +189,7 @@ private void writeToFile(Set microservices, String fileName) { MicroserviceSystem microserviceSystem = new MicroserviceSystem(config.getSystemName(), commitID, microservices, new HashSet<>()); - JsonReadWriteUtils.writeToJSON("./output/" + fileName, microserviceSystem.toJsonObject()); + JsonReadWriteUtils.writeToJSON(fileName, microserviceSystem.toJsonObject()); LoggerManager.info(() -> "Successfully extracted IR at " + commitID); } diff --git a/src/main/java/edu/university/ecs/lab/intermediate/merge/IRMergeRunner.java b/src/main/java/edu/university/ecs/lab/intermediate/merge/IRMergeRunner.java index 6059520a..5ab74354 100644 --- a/src/main/java/edu/university/ecs/lab/intermediate/merge/IRMergeRunner.java +++ b/src/main/java/edu/university/ecs/lab/intermediate/merge/IRMergeRunner.java @@ -25,13 +25,13 @@ public static void main(String[] args) throws IOException { String[] finalArgs = args; LoggerManager.info(() -> "IRMergeRunner starting... args: " + Arrays.toString(finalArgs)); - if (args.length != 3) { + if (args.length != 4) { Error.reportAndExit(Error.INVALID_ARGS, Optional.empty()); } Config config = ConfigUtil.readConfig(args[0]); - MergeService mergeService = new MergeService(args[0], args[1], args[2]); + MergeService mergeService = new MergeService(args[0], args[1], args[2], args[3]); //mergeService.generateMergeIR(); } diff --git a/src/main/java/edu/university/ecs/lab/intermediate/merge/services/MergeService.java b/src/main/java/edu/university/ecs/lab/intermediate/merge/services/MergeService.java index 6b9cb884..2b58b869 100644 --- a/src/main/java/edu/university/ecs/lab/intermediate/merge/services/MergeService.java +++ b/src/main/java/edu/university/ecs/lab/intermediate/merge/services/MergeService.java @@ -23,15 +23,18 @@ public class MergeService { private final Config config; private final MicroserviceSystem microserviceSystem; private final SystemChange systemChange; + private final String outputPath; // TODO handle exceptions here public MergeService( String intermediatePath, String deltaPath, - String configPath) { + String configPath, + String outputPath) { this.config = ConfigUtil.readConfig(configPath); this.microserviceSystem = JsonReadWriteUtils.readFromJSON(Path.of(intermediatePath).toAbsolutePath().toString(), MicroserviceSystem.class); this.systemChange = JsonReadWriteUtils.readFromJSON(Path.of(deltaPath).toAbsolutePath().toString(), SystemChange.class); + this.outputPath = outputPath.isEmpty() ? "./NewIR.json" : outputPath; } /** @@ -42,8 +45,7 @@ public void generateMergeIR(String newCommitID) { // If no changes are present we will write back out same IR if (Objects.isNull(systemChange.getChanges())) { LoggerManager.debug(() -> "No changes found at " + systemChange.getOldCommit() + " -> " + systemChange.getNewCommit()); - String filePath = "./output/IR_" + newCommitID + ".json"; - JsonReadWriteUtils.writeToJSON(filePath, microserviceSystem); + JsonReadWriteUtils.writeToJSON(outputPath, microserviceSystem); return; } @@ -70,8 +72,7 @@ public void generateMergeIR(String newCommitID) { microserviceSystem.setCommitID(systemChange.getNewCommit()); LoggerManager.info(() -> "Merged to new IR at " + systemChange.getNewCommit()); - String filePath = "./output/IR_" + newCommitID + ".json"; - JsonReadWriteUtils.writeToJSON(filePath, microserviceSystem); + JsonReadWriteUtils.writeToJSON(outputPath, microserviceSystem); } diff --git a/src/main/java/edu/university/ecs/lab/intermediate/utils/StringParserUtils.java b/src/main/java/edu/university/ecs/lab/intermediate/utils/StringParserUtils.java index d7958bce..e3bbaa3f 100644 --- a/src/main/java/edu/university/ecs/lab/intermediate/utils/StringParserUtils.java +++ b/src/main/java/edu/university/ecs/lab/intermediate/utils/StringParserUtils.java @@ -28,39 +28,6 @@ public static String removeOuterQuotations(String s) { return s; } -// /** -// * Merge the given class and method paths into a single path. -// * -// *

ex: /abc/def and ghi/jkl --> abc/def/ghi/jkl -// * -// * @param classPath the class base (api) path -// * @param methodPath the method (api) path -// * @return the merged path -// */ -// public static String mergePaths(String classPath, String methodPath) { -// if (classPath.startsWith("/")) classPath = classPath.substring(1); -// if (methodPath.startsWith("/")) methodPath = methodPath.substring(1); -// -// String path = -// FilenameUtils.normalizeNoEndSeparator(FilenameUtils.concat(classPath, methodPath), true); -// if (!path.startsWith("/")) path = "/" + path; -// -// return path; -// } -// -// /** -// * Find the package name in the given compilation unit. -// * -// * @param cu the compilation unit -// * @return the package name else null if not found -// */ -// public static String findPackage(CompilationUnit cu) { -// for (PackageDeclaration pd : cu.findAll(PackageDeclaration.class)) { -// return pd.getNameAsString(); -// } -// return null; -// } - /** * Simplifies all path arguments to {?}. * diff --git a/src/test/java/integration/IRComparisonTest.java b/src/test/java/integration/IRComparisonTest.java index b8345fec..7667ee18 100644 --- a/src/test/java/integration/IRComparisonTest.java +++ b/src/test/java/integration/IRComparisonTest.java @@ -1,32 +1,35 @@ -//package integration; -// -//import edu.university.ecs.lab.common.models.ir.*; -//import edu.university.ecs.lab.common.services.GitService; -//import edu.university.ecs.lab.common.utils.FileUtils; -//import edu.university.ecs.lab.common.utils.JsonReadWriteUtils; -//import edu.university.ecs.lab.delta.services.DeltaExtractionService; -//import edu.university.ecs.lab.intermediate.create.services.IRExtractionService; -//import edu.university.ecs.lab.intermediate.merge.services.MergeService; -//import org.eclipse.jgit.revwalk.RevCommit; -//import org.junit.jupiter.api.Assertions; -//import org.junit.jupiter.api.BeforeAll; -//import org.junit.jupiter.api.Test; -// -//import java.io.IOException; -//import java.nio.file.Files; -//import java.nio.file.Paths; -//import java.nio.file.StandardCopyOption; -//import java.util.*; -// -//import static integration.Constants.*; -// -//class IRComparisonTest { -// private static IRExtractionService irExtractionService; -// private static DeltaExtractionService deltaExtractionService; -// private static List list; -// private static GitService gitService; -// -// +package integration; + +import edu.university.ecs.lab.common.models.ir.*; +import edu.university.ecs.lab.common.services.GitService; +import edu.university.ecs.lab.common.utils.FileUtils; +import edu.university.ecs.lab.common.utils.JsonReadWriteUtils; +import edu.university.ecs.lab.delta.services.DeltaExtractionService; +import edu.university.ecs.lab.intermediate.create.services.IRExtractionService; +import edu.university.ecs.lab.intermediate.merge.services.MergeService; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Ignore; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.*; + +import static integration.Constants.*; + + +@Ignore +class IRComparisonTest { + private static IRExtractionService irExtractionService; + private static DeltaExtractionService deltaExtractionService; + private static List list; + private static GitService gitService; + + // @BeforeAll // public static void setUp() { // FileUtils.makeDirs(); @@ -36,93 +39,93 @@ // // irExtractionService = new IRExtractionService(TEST_CONFIG_PATH, Optional.of(list.get(0).toString().split(" ")[1])); // -// irExtractionService.generateIR(OLD_IR_NAME); +// irExtractionService.generateIR("./output/OldIR.json"); // } -// + // @Test -// void testComparison() { -// -// // Loop through commit history and create delta, merge, etc... -// for (int i = 0; i < list.size() - 1; i++) { -// String commitIdOld = list.get(i).toString().split(" ")[1]; -// String commitIdNew = list.get(i + 1).toString().split(" ")[1]; -// -// // Extract changes from one commit to the other -// deltaExtractionService = new DeltaExtractionService(TEST_CONFIG_PATH, OLD_IR_PATH, commitIdOld, commitIdNew); -// deltaExtractionService.generateDelta(); -// -// // Merge Delta changes to old IR to create new IR representing new commit changes -// MergeService mergeService = new MergeService(OLD_IR_PATH, DELTA_PATH, TEST_CONFIG_PATH); -// mergeService.generateMergeIR(commitIdNew); -// -// if(i < list.size() - 2) { -// try { -// Files.move(Paths.get(NEW_IR_PATH), Paths.get(OLD_IR_PATH), StandardCopyOption.REPLACE_EXISTING); -// } catch (IOException e) { -// e.printStackTrace(); + void testComparison() { + + // Loop through commit history and create delta, merge, etc... + for (int i = 0; i < list.size() - 1; i++) { + String commitIdOld = list.get(i).toString().split(" ")[1]; + String commitIdNew = list.get(i + 1).toString().split(" ")[1]; + + // Extract changes from one commit to the other + deltaExtractionService = new DeltaExtractionService(TEST_CONFIG_PATH, "./output/Delta.json", commitIdOld, commitIdNew); + deltaExtractionService.generateDelta(); + + // Merge Delta changes to old IR to create new IR representing new commit changes + MergeService mergeService = new MergeService("./output/OldIR.json", "./output/Delta.json", TEST_CONFIG_PATH, "./output/NewIR.json"); + mergeService.generateMergeIR(commitIdNew); + + if(i < list.size() - 2) { + try { + Files.move(Paths.get("./output/NewIR.json"), Paths.get("./output/OldIR.json"), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + // Create IR of last commit + irExtractionService = new IRExtractionService(TEST_CONFIG_PATH, Optional.of(list.get(list.size() - 1).toString().split(" ")[1])); + irExtractionService.generateIR("./output/TestIR.json"); + + // Compare two IR's for equivalence + MicroserviceSystem microserviceSystem1 = JsonReadWriteUtils.readFromJSON("./output/NewIR.json", MicroserviceSystem.class); + MicroserviceSystem microserviceSystem2 = JsonReadWriteUtils.readFromJSON("./output/TestIR.json", MicroserviceSystem.class); + + microserviceSystem1.setOrphans(new HashSet<>()); + microserviceSystem2.setOrphans(new HashSet<>()); + + Assertions.assertTrue(Objects.deepEquals(microserviceSystem1, microserviceSystem2)); + + } + + +// private static void deepCompareSystems(MicroserviceSystem microserviceSystem1, MicroserviceSystem microserviceSystem2) { +// // Ignore orphans for testing +// microserviceSystem1.setOrphans(null); +// microserviceSystem2.setOrphans(null); +// System.out.println("System equivalence is: " + Objects.deepEquals(microserviceSystem1, microserviceSystem2)); +// +// for (Microservice microservice1 : microserviceSystem1.getMicroservices()) { +// outer2: { +// for (Microservice microservice2 : microserviceSystem2.getMicroservices()) { +// if (microservice1.getName().equals(microservice2.getName())) { +// System.out.println("Microservice equivalence of " + microservice1.getPath() + " is: " + Objects.deepEquals(microservice1, microservice2)); +// for (ProjectFile projectFile1 : microservice1.getAllFiles()) { +// outer1: { +// for (ProjectFile projectFile2 : microservice2.getAllFiles()) { +// if (projectFile1.getPath().equals(projectFile2.getPath())) { +// System.out.println("Class equivalence of " + projectFile1.getPath() + " is: " + Objects.deepEquals(projectFile1, projectFile2)); +// break outer1; +// } +// } +// +// System.out.println("No JClass match found for " + projectFile1.getPath()); +// } +// } +// break outer2; +// } // } -// } -// } -// -// // Create IR of last commit -// irExtractionService = new IRExtractionService(TEST_CONFIG_PATH, Optional.of(list.get(list.size() - 1).toString().split(" ")[1])); -// irExtractionService.generateIR(TEST_IR_NAME); -// -// // Compare two IR's for equivalence -// MicroserviceSystem microserviceSystem1 = JsonReadWriteUtils.readFromJSON(NEW_IR_PATH, MicroserviceSystem.class); -// MicroserviceSystem microserviceSystem2 = JsonReadWriteUtils.readFromJSON(TEST_IR_PATH, MicroserviceSystem.class); -// -// microserviceSystem1.setOrphans(new HashSet<>()); -// microserviceSystem2.setOrphans(new HashSet<>()); -// -// Assertions.assertTrue(Objects.deepEquals(microserviceSystem1, microserviceSystem2)); -// -// } -// -// -//// private static void deepCompareSystems(MicroserviceSystem microserviceSystem1, MicroserviceSystem microserviceSystem2) { -//// // Ignore orphans for testing -//// microserviceSystem1.setOrphans(null); -//// microserviceSystem2.setOrphans(null); -//// System.out.println("System equivalence is: " + Objects.deepEquals(microserviceSystem1, microserviceSystem2)); -//// -//// for (Microservice microservice1 : microserviceSystem1.getMicroservices()) { -//// outer2: { -//// for (Microservice microservice2 : microserviceSystem2.getMicroservices()) { -//// if (microservice1.getName().equals(microservice2.getName())) { -//// System.out.println("Microservice equivalence of " + microservice1.getPath() + " is: " + Objects.deepEquals(microservice1, microservice2)); -//// for (ProjectFile projectFile1 : microservice1.getAllFiles()) { -//// outer1: { -//// for (ProjectFile projectFile2 : microservice2.getAllFiles()) { -//// if (projectFile1.getPath().equals(projectFile2.getPath())) { -//// System.out.println("Class equivalence of " + projectFile1.getPath() + " is: " + Objects.deepEquals(projectFile1, projectFile2)); -//// break outer1; -//// } -//// } -//// -//// System.out.println("No JClass match found for " + projectFile1.getPath()); -//// } -//// } -//// break outer2; -//// } -//// } -//// -//// System.out.println("No Microservice match found for " + microservice1.getPath()); -//// } -//// } -//// -//// } // -// private static List iterableToList(Iterable iterable) { -// Iterator iterator = iterable.iterator(); -// List list = new LinkedList<>(); -// while (iterator.hasNext()) { -// list.add(iterator.next()); +// System.out.println("No Microservice match found for " + microservice1.getPath()); +// } // } -// Collections.reverse(list); // -// return list; // } -// -// -//} + + private static List iterableToList(Iterable iterable) { + Iterator iterator = iterable.iterator(); + List list = new LinkedList<>(); + while (iterator.hasNext()) { + list.add(iterator.next()); + } + Collections.reverse(list); + + return list; + } + + +} diff --git a/src/test/java/unit/antipatterns/CyclicDependencyTest.java b/src/test/java/unit/antipatterns/CyclicDependencyTest.java index 70dfd3d1..0fdf9dde 100644 --- a/src/test/java/unit/antipatterns/CyclicDependencyTest.java +++ b/src/test/java/unit/antipatterns/CyclicDependencyTest.java @@ -3,16 +3,18 @@ import static org.junit.jupiter.api.Assertions.*; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; -import java.util.Optional; +import java.util.*; +import edu.university.ecs.lab.common.models.enums.ClassRole; +import edu.university.ecs.lab.common.models.enums.HttpMethod; +import edu.university.ecs.lab.common.models.ir.*; +import edu.university.ecs.lab.detection.antipatterns.models.ServiceChain; import edu.university.ecs.lab.detection.antipatterns.services.CyclicDependencyMSLevelService; +import edu.university.ecs.lab.detection.antipatterns.services.ServiceChainMSLevelService; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; -import edu.university.ecs.lab.common.models.ir.MicroserviceSystem; import edu.university.ecs.lab.common.models.sdg.ServiceDependencyGraph; import edu.university.ecs.lab.common.utils.FileUtils; import edu.university.ecs.lab.common.utils.JsonReadWriteUtils; @@ -20,23 +22,25 @@ import edu.university.ecs.lab.intermediate.create.services.IRExtractionService; import unit.Constants; + +@Ignore public class CyclicDependencyTest { private CyclicDependencyMSLevelService cyclicService; private ServiceDependencyGraph sdg; @Before public void setUp(){ - FileUtils.makeDirs(); - - IRExtractionService irExtractionService = new IRExtractionService(Constants.TEST_CONFIG_PATH, Optional.empty()); - - irExtractionService.generateIR("TestIR.json"); - - MicroserviceSystem microserviceSystem = JsonReadWriteUtils.readFromJSON("./output/TestIR.json", MicroserviceSystem.class); - - sdg = new ServiceDependencyGraph(microserviceSystem); - - cyclicService = new CyclicDependencyMSLevelService(); +// FileUtils.makeDirs(); +// +// IRExtractionService irExtractionService = new IRExtractionService(Constants.TEST_CONFIG_PATH, Optional.empty()); +// +// irExtractionService.generateIR("TestIR.json"); +// +// MicroserviceSystem microserviceSystem = JsonReadWriteUtils.readFromJSON("./output/TestIR.json", MicroserviceSystem.class); +// +// sdg = new ServiceDependencyGraph(microserviceSystem); +// +// cyclicService = new CyclicDependencyMSLevelService(); } public void testCyclicDependencyDetection(){ @@ -50,5 +54,159 @@ public void testCyclicDependencyDetection(){ assertTrue(Objects.equals(cyclicDep.getCycles(), expectedCyclicDep)); } + + @Test + public void cyclicDependencyHasNOne() { + Microservice microservice1 = new Microservice("ms1", "/ms1"); + JClass jClass1 = new JClass("class1", "/class1","class1", ClassRole.CONTROLLER); + jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); + microservice1.addJClass(jClass1); + JClass jClass5 = new JClass("class5", "/class5","class5", ClassRole.SERVICE); + jClass5.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + microservice1.addJClass(jClass5); + + Microservice microservice2 = new Microservice("ms2", "/ms2"); + JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); + jClass2.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + microservice2.addJClass(jClass2); + JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); + jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); + microservice2.addJClass(jClass3); + + Microservice microservice3 = new Microservice("ms3", "/ms3"); + JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); + jClass4.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + microservice3.addJClass(jClass4); + + + MicroserviceSystem microserviceSystem1 = new MicroserviceSystem("test", "1", Set.of(microservice1, microservice2, microservice3), new HashSet<>()); + ServiceDependencyGraph sdg1 = new ServiceDependencyGraph(microserviceSystem1); + CyclicDependencyMSLevelService cs1 = new CyclicDependencyMSLevelService(); + + CyclicDependency cd = cs1.findCyclicDependencies(sdg1); + assertTrue(cd.getCycles().size() == 0); + + } + + @Test + public void cyclicDependencyHasOne() { + Microservice microservice1 = new Microservice("ms1", "/ms1"); + JClass jClass1 = new JClass("class1", "/class1","class1", ClassRole.CONTROLLER); + jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); + microservice1.addJClass(jClass1); + JClass jClass5 = new JClass("class5", "/class5","class5", ClassRole.SERVICE); + jClass5.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + microservice1.addJClass(jClass5); + + Microservice microservice2 = new Microservice("ms2", "/ms2"); + JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); + jClass2.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + microservice2.addJClass(jClass2); + JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); + jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); + microservice2.addJClass(jClass3); + + Microservice microservice3 = new Microservice("ms3", "/ms3"); + JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); + jClass4.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + microservice3.addJClass(jClass4); + JClass jClass6 = new JClass("class6", "/class6","class6", ClassRole.CONTROLLER); + jClass6.setMethods(Set.of(new Endpoint(new Method(), "/endpoint3", HttpMethod.GET))); + microservice3.addJClass(jClass6); + + + MicroserviceSystem microserviceSystem1 = new MicroserviceSystem("test", "1", Set.of(microservice1, microservice2, microservice3), new HashSet<>()); + ServiceDependencyGraph sdg1 = new ServiceDependencyGraph(microserviceSystem1); + CyclicDependencyMSLevelService cs1 = new CyclicDependencyMSLevelService(); + + CyclicDependency cd = cs1.findCyclicDependencies(sdg1); + assertTrue(cd.getCycles().size() == 1); + assertTrue(cd.getCycles().get(0).size() == 3); + assertTrue(cd.getCycles().get(0).containsAll(List.of("ms1", "ms2", "ms3"))); + + } + + @Test + public void cyclicDependencyHasOneAlso() { + Microservice microservice1 = new Microservice("ms1", "/ms1"); + JClass jClass1 = new JClass("class1", "/class1","class1", ClassRole.CONTROLLER); + jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); + microservice1.addJClass(jClass1); + JClass jClass2 = new JClass("class5", "/class2","class5", ClassRole.SERVICE); + jClass2.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + microservice1.addJClass(jClass2); + + Microservice microservice2 = new Microservice("ms2", "/ms2"); + JClass jClass3 = new JClass("class2", "/class3","class2", ClassRole.SERVICE); + jClass3.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + microservice2.addJClass(jClass3); + JClass jClass4 = new JClass("class3", "/class4","class3", ClassRole.CONTROLLER); + jClass4.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); + microservice2.addJClass(jClass4); + + + + + MicroserviceSystem microserviceSystem1 = new MicroserviceSystem("test", "1", Set.of(microservice1, microservice2), new HashSet<>()); + ServiceDependencyGraph sdg1 = new ServiceDependencyGraph(microserviceSystem1); + CyclicDependencyMSLevelService cs1 = new CyclicDependencyMSLevelService(); + + CyclicDependency cd = cs1.findCyclicDependencies(sdg1); + assertTrue(cd.getCycles().size() == 1); + assertTrue(cd.getCycles().get(0).size() == 2); + assertTrue(cd.getCycles().get(0).containsAll(List.of("ms1", "ms2"))); + + } + + @Test + public void cyclicDependencyHasTwo() { + Microservice microservice1 = new Microservice("ms1", "/ms1"); + JClass jClass1 = new JClass("class1", "/class1","class1", ClassRole.CONTROLLER); + jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); + microservice1.addJClass(jClass1); + JClass jClass5 = new JClass("class5", "/class5","class5", ClassRole.SERVICE); + jClass5.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + microservice1.addJClass(jClass5); + + Microservice microservice2 = new Microservice("ms2", "/ms2"); + JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); + jClass2.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + microservice2.addJClass(jClass2); + JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); + jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); + microservice2.addJClass(jClass3); + + Microservice microservice3 = new Microservice("ms3", "/ms3"); + JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); + jClass4.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + microservice3.addJClass(jClass4); + JClass jClass9 = new JClass("class9", "/class9","class9", ClassRole.SERVICE); + jClass9.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint4", HttpMethod.GET))); + microservice3.addJClass(jClass9); + JClass jClass6 = new JClass("class6", "/class6","class6", ClassRole.CONTROLLER); + jClass6.setMethods(Set.of(new Endpoint(new Method(), "/endpoint3", HttpMethod.GET))); + microservice3.addJClass(jClass6); + + Microservice microservice4 = new Microservice("ms4", "/ms4"); + JClass jClass7 = new JClass("jClass7", "/jClass7","jClass7", ClassRole.SERVICE); + jClass7.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + microservice4.addJClass(jClass7); + JClass jClass8 = new JClass("jClass8", "/jClass8","jClass8", ClassRole.CONTROLLER); + jClass8.setMethods(Set.of(new Endpoint(new Method(), "/endpoint4", HttpMethod.GET))); + microservice4.addJClass(jClass8); + + + MicroserviceSystem microserviceSystem1 = new MicroserviceSystem("test", "1", Set.of(microservice1, microservice2, microservice3, microservice4), new HashSet<>()); + ServiceDependencyGraph sdg1 = new ServiceDependencyGraph(microserviceSystem1); + CyclicDependencyMSLevelService cs1 = new CyclicDependencyMSLevelService(); + + CyclicDependency cd = cs1.findCyclicDependencies(sdg1); + assertTrue(cd.getCycles().size() == 2); + assertTrue(cd.getCycles().get(0).size() == 3); + assertTrue(cd.getCycles().get(1).size() == 2); + assertTrue(cd.getCycles().get(0).containsAll(List.of("ms1", "ms2", "ms3"))); + assertTrue(cd.getCycles().get(1).containsAll(List.of("ms3", "ms4"))); + + } } diff --git a/src/test/java/unit/antipatterns/GreedyMicroserviceTest.java b/src/test/java/unit/antipatterns/GreedyMicroserviceTest.java index f885ace5..af79ccb0 100644 --- a/src/test/java/unit/antipatterns/GreedyMicroserviceTest.java +++ b/src/test/java/unit/antipatterns/GreedyMicroserviceTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import edu.university.ecs.lab.common.models.ir.MicroserviceSystem; @@ -16,6 +17,8 @@ import java.util.*; + +@Ignore public class GreedyMicroserviceTest { private GreedyService greedyService; private ServiceDependencyGraph sdg; diff --git a/src/test/java/unit/antipatterns/HubLikeMicroserviceTest.java b/src/test/java/unit/antipatterns/HubLikeMicroserviceTest.java index 3ca4c74b..71618e81 100644 --- a/src/test/java/unit/antipatterns/HubLikeMicroserviceTest.java +++ b/src/test/java/unit/antipatterns/HubLikeMicroserviceTest.java @@ -5,6 +5,7 @@ import java.util.*; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import edu.university.ecs.lab.common.models.ir.MicroserviceSystem; @@ -16,6 +17,8 @@ import edu.university.ecs.lab.intermediate.create.services.IRExtractionService; import unit.Constants; + +@Ignore public class HubLikeMicroserviceTest { private HubLikeService hubLikeService; private ServiceDependencyGraph sdg; diff --git a/src/test/java/unit/antipatterns/NoApiGatewayTest.java b/src/test/java/unit/antipatterns/NoApiGatewayTest.java index 4e478e00..06f35945 100644 --- a/src/test/java/unit/antipatterns/NoApiGatewayTest.java +++ b/src/test/java/unit/antipatterns/NoApiGatewayTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.*; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import edu.university.ecs.lab.common.models.ir.MicroserviceSystem; import edu.university.ecs.lab.common.utils.FileUtils; @@ -14,6 +15,8 @@ import java.util.Optional; + +@Ignore public class NoApiGatewayTest { private NoApiGatewayService noApiGatewayService; private MicroserviceSystem microserviceSystem; diff --git a/src/test/java/unit/antipatterns/ServiceChainTest.java b/src/test/java/unit/antipatterns/ServiceChainTest.java index a2b963ac..62bc0442 100644 --- a/src/test/java/unit/antipatterns/ServiceChainTest.java +++ b/src/test/java/unit/antipatterns/ServiceChainTest.java @@ -2,13 +2,16 @@ import static org.junit.jupiter.api.Assertions.*; +import edu.university.ecs.lab.common.models.enums.ClassRole; +import edu.university.ecs.lab.common.models.enums.HttpMethod; +import edu.university.ecs.lab.common.models.ir.*; import edu.university.ecs.lab.detection.antipatterns.services.ServiceChainMSLevelService; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.util.*; -import edu.university.ecs.lab.common.models.ir.MicroserviceSystem; import edu.university.ecs.lab.common.models.sdg.ServiceDependencyGraph; import edu.university.ecs.lab.common.utils.FileUtils; import edu.university.ecs.lab.common.utils.JsonReadWriteUtils; @@ -16,6 +19,8 @@ import edu.university.ecs.lab.intermediate.create.services.IRExtractionService; import unit.Constants; + +@Ignore public class ServiceChainTest { private ServiceChainMSLevelService serviceChainService; private ServiceDependencyGraph sdg; @@ -73,4 +78,70 @@ public void testEmptyGraph() { public void testNullGraph() { assertThrows(NullPointerException.class, () -> serviceChainService.getServiceChains(null)); } + + @Test + public void serviceChainHasOne() { + Microservice microservice1 = new Microservice("ms1", "/ms1"); + JClass jClass1 = new JClass("class1", "/class1","class1", ClassRole.CONTROLLER); + jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); + microservice1.addJClass(jClass1); + + Microservice microservice2 = new Microservice("ms2", "/ms2"); + JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); + jClass2.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + microservice2.addJClass(jClass2); + JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); + jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); + microservice2.addJClass(jClass3); + + Microservice microservice3 = new Microservice("ms3", "/ms3"); + JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); + jClass4.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + microservice3.addJClass(jClass4); + + MicroserviceSystem microserviceSystem1 = new MicroserviceSystem("test", "1", Set.of(microservice1, microservice2, microservice3), new HashSet<>()); + ServiceDependencyGraph sdg1 = new ServiceDependencyGraph(microserviceSystem1); + ServiceChainMSLevelService scs1 = new ServiceChainMSLevelService(); + + ServiceChain sc1 = scs1.getServiceChains(sdg1); + + assertTrue(sc1.getChain().size() == 1); + assertEquals(sc1.getChain().get(0), List.of("ms3", "ms2", "ms1")); + } + + @Test + public void serviceChainHasNoneCycle() { + Microservice microservice1 = new Microservice("ms1", "/ms1"); + JClass jClass1 = new JClass("class1", "/class1","class1", ClassRole.CONTROLLER); + jClass1.setMethods(Set.of(new Endpoint(new Method(), "/endpoint1", HttpMethod.GET))); + microservice1.addJClass(jClass1); + JClass jClass5 = new JClass("class5", "/class5","class5", ClassRole.SERVICE); + jClass5.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint3", HttpMethod.GET))); + microservice1.addJClass(jClass5); + + Microservice microservice2 = new Microservice("ms2", "/ms2"); + JClass jClass2 = new JClass("class2", "/class2","class2", ClassRole.SERVICE); + jClass2.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint1", HttpMethod.GET))); + microservice2.addJClass(jClass2); + JClass jClass3 = new JClass("class3", "/class3","class3", ClassRole.CONTROLLER); + jClass3.setMethods(Set.of(new Endpoint(new Method(), "/endpoint2", HttpMethod.GET))); + microservice2.addJClass(jClass3); + + Microservice microservice3 = new Microservice("ms3", "/ms3"); + JClass jClass4 = new JClass("class4", "/class4","class4", ClassRole.SERVICE); + jClass4.setMethodCalls(List.of(new RestCall(new MethodCall(), "/endpoint2", HttpMethod.GET))); + microservice3.addJClass(jClass4); + JClass jClass6 = new JClass("class6", "/class6","class6", ClassRole.CONTROLLER); + jClass6.setMethods(Set.of(new Endpoint(new Method(), "/endpoint3", HttpMethod.GET))); + microservice3.addJClass(jClass6); + + + MicroserviceSystem microserviceSystem1 = new MicroserviceSystem("test", "1", Set.of(microservice1, microservice2, microservice3), new HashSet<>()); + ServiceDependencyGraph sdg1 = new ServiceDependencyGraph(microserviceSystem1); + ServiceChainMSLevelService scs1 = new ServiceChainMSLevelService(); + + ServiceChain sc1 = scs1.getServiceChains(sdg1); + assertTrue(sc1.getChain().isEmpty()); + + } } diff --git a/src/test/java/unit/extraction/ExtractionTest.java b/src/test/java/unit/extraction/ExtractionTest.java index cd07c57b..7359d0a9 100644 --- a/src/test/java/unit/extraction/ExtractionTest.java +++ b/src/test/java/unit/extraction/ExtractionTest.java @@ -1,40 +1,58 @@ -//package unit.extraction; -// -//import edu.university.ecs.lab.common.config.ConfigUtil; -//import edu.university.ecs.lab.common.models.ir.JClass; -//import edu.university.ecs.lab.common.models.ir.RestCall; -//import edu.university.ecs.lab.common.services.GitService; -//import edu.university.ecs.lab.common.utils.SourceToObjectUtils; -//import org.junit.Before; -//import org.junit.Test; -// -//import java.io.File; -// -//import static org.junit.Assert.assertEquals; -//import static org.junit.Assert.assertTrue; -// -//public class ExtractionTest { -// private static final String TEST_FILE = "src/test/resources/TestFile.java"; -// private static final String TEST_CONFIG_FILE = "src/test/resources/test_config.json"; -// private static final int EXPECTED_CALLS = 6; -// private static final String PRE_URL = "/api/v1/seatservice/test"; -// -// @Before -// public void setUp() { -// } -// -// @Test -// public void restCallExtractionTest1() { -// JClass jClass = SourceToObjectUtils.parseClass(new File(TEST_FILE), ConfigUtil.readConfig(TEST_CONFIG_FILE), ""); -// -// assertEquals(EXPECTED_CALLS, jClass.getRestCalls().size()); +package unit.extraction; + +import edu.university.ecs.lab.common.config.ConfigUtil; +import edu.university.ecs.lab.common.models.ir.*; +import edu.university.ecs.lab.common.services.GitService; +import edu.university.ecs.lab.common.utils.JsonReadWriteUtils; +import edu.university.ecs.lab.common.utils.SourceToObjectUtils; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +@Ignore +public class ExtractionTest { + private static final String TEST_FILE1 = "src/test/resources/TestFile2.java"; + private static final String TEST_FILE2 = "src/test/resources/TestFile3.java"; + + + private static final String TEST_CONFIG_FILE = "src/test/resources/test_config.json"; + private static final int EXPECTED_CALLS = 6; + private static final String PRE_URL = "/api/v1/seatservice/test"; + + @Before + public void setUp() { + } + + @Test + public void restCallExtractionTest1() { + GitService gitService = new GitService(TEST_CONFIG_FILE); + MicroserviceSystem ms1 = JsonReadWriteUtils.readFromJSON("C:\\Users\\ninja\\IdeaProjects\\cimet2\\output\\java-microservice\\IR\\IR1_8948.json", MicroserviceSystem.class); + JClass jClass1 = SourceToObjectUtils.parseClass(new File(TEST_FILE1), ConfigUtil.readConfig(TEST_CONFIG_FILE), ""); + JClass jClass2 = SourceToObjectUtils.parseClass(new File(TEST_FILE2), ConfigUtil.readConfig(TEST_CONFIG_FILE), ""); + + for(Endpoint e : jClass1.getEndpoints()) { + for(RestCall rc : jClass2.getRestCalls()) { + if(RestCall.matchEndpoint(rc, e)) { + System.out.println("Passed " + rc.getUrl() + " " + e.getUrl()); + + } + } + + } + // // int i = 1; // for(RestCall restCall : jClass.getRestCalls()) { // assertTrue(restCall.getUrl().startsWith(PRE_URL + i++)); // } -// -// } -// -// -//} + + } + + +} diff --git a/src/test/resources/TestFile2.java b/src/test/resources/TestFile2.java new file mode 100644 index 00000000..d24a115f --- /dev/null +++ b/src/test/resources/TestFile2.java @@ -0,0 +1,108 @@ +package com.apssouza.controllers; + +import com.apssouza.services.TodoService; +import com.apssouza.entities.ToDo; +import com.apssouza.events.TodoCreatedEvent; +import com.apssouza.exceptions.DataNotFoundException; +import com.apssouza.infra.EventPublisher; +import com.fasterxml.jackson.databind.JsonNode; +import java.net.URI; +import java.util.List; +import java.util.Optional; +import javax.validation.Valid; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + +/** + * Controller responsible of accessing ToDo's functionalities + * + * @author apssouza + */ +@RequestMapping("/todos") +@RestController +public class TodoController { + + @Autowired + TodoService todoService; + + @Autowired + EventPublisher publisher; + + @GetMapping + public List all() { + return this.todoService.all(); + } + + @GetMapping("search") + public List getByUserEmail(@RequestParam("email") String email) { + return this.todoService.getByUserEmail(email); + } + + @PostMapping + public ResponseEntity save(@RequestBody @Valid ToDo todo, BindingResult result) { + if (result.hasErrors()) { + return ResponseEntity.badRequest().build(); + } + ToDo saved = this.todoService.save(todo); + Long id = saved.getId(); + if (id != null) { + URI location = ServletUriComponentsBuilder + .fromCurrentRequest().path("/{id}") + .buildAndExpand(id).toUri(); + return ResponseEntity.created(location).build(); + } + return ResponseEntity.noContent().build(); + } + + @PutMapping("{id}") + public ResponseEntity update( + @PathVariable long id, + @RequestBody @Valid ToDo toDo + ) { + return ResponseEntity.ok(todoService.update(id, toDo)); + } + + @GetMapping("{id}") + public ResponseEntity find(@PathVariable long id) { + Optional findById = todoService.findById(id); + return findById.map(todo -> { + return ResponseEntity.ok(todo); + }).orElseThrow(() -> new DataNotFoundException("Todo not found")); + } + + @DeleteMapping("{id}") + public ResponseEntity delete(@PathVariable long id) { + todoService.delete(id); + return ResponseEntity.status(HttpStatus.OK).build(); + } + + @PutMapping("{id}/status") + public ResponseEntity statusUpdate(@PathVariable long id, @RequestBody JsonNode statusUpdate) { + JsonNode status = statusUpdate.get("status"); + if (status == null) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST). + header("reason", "JSON should contains field done"). + build(); + } + ToDo todo = todoService.updateStatus( + id, + ToDo.TodoStatus.valueOf(status.asText()) + ); + return ResponseEntity.ok(todo); + } + +} \ No newline at end of file diff --git a/src/test/resources/TestFile3.java b/src/test/resources/TestFile3.java new file mode 100644 index 00000000..d9d3a7c2 --- /dev/null +++ b/src/test/resources/TestFile3.java @@ -0,0 +1,30 @@ +package com.apssouza.clients; + +import com.apssouza.pojos.Todo; +import com.apssouza.pojos.User; +import java.util.List; +import org.springframework.cloud.netflix.feign.FeignClient; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * Declarative To do REST client + * + * @author apssouza + */ +@FeignClient("todo") +public interface TodoClient { + + @RequestMapping(value = "/todos", method = RequestMethod.GET) + public List getAll(); + + @RequestMapping(value = "/todos/search", method = RequestMethod.GET) + public List getTodoByUserEmaill(@RequestParam("email") String email); + + @RequestMapping( + value = "/todos", + method = RequestMethod.POST + ) + Todo createTodo(Todo todo); +} \ No newline at end of file diff --git a/src/test/resources/test_config.json b/src/test/resources/test_config.json index 2d86cccc..9c3ce942 100644 --- a/src/test/resources/test_config.json +++ b/src/test/resources/test_config.json @@ -1,5 +1,5 @@ { - "systemName": "Train-ticket", + "systemName": "train-ticket", "repositoryURL": "https://github.com/FudanSELab/train-ticket.git", - "baseBranch": "main" + "baseBranch": "master" } \ No newline at end of file