diff --git a/.gitignore b/.gitignore
index f19a265..3973d8e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
+goblinWeaver_data
### STS ###
.apt_generated
@@ -31,4 +32,4 @@ build/
### VS Code ###
.vscode/
-osvData/
\ No newline at end of file
+osvData/
diff --git a/pom.xml b/pom.xml
index b2d4c14..256c908 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
com.cifre.sap.su
goblinWeaver
- 1.0.0
+ 2.0.0
goblinWeaver
Weaver to query Neo4j ecosystem dependency graph
diff --git a/readme.md b/readme.md
index 8d9894d..b1554e6 100644
--- a/readme.md
+++ b/readme.md
@@ -1,21 +1,31 @@
-# Gobelin Weaver
+# Goblin Weaver
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE.txt)
-The Weaver is a REST API that takes a Cypher query and desired additional information as input and returns the result of this query on a Neo4j ecosystem dependency graph, enriching it on the fly according to the user’s needs.
+The Weaver is a REST API for querying the Maven Central's dependency graph and enriching it by adding information to it according to the user's needs.
-This weaver comes in addition to the ecosystem dependency graph miner available here: https://github.com/Goblin-Ecosystem/goblinWeaver.
-An example of usage of this tool is available here: https://github.com/Goblin-Ecosystem/mavenDatasetExperiences
-A Zenodo archive that contains the associated dataset dump and the Weaver jar is available here: https://zenodo.org/records/10291589.
+This weaver use the Maven ecosystem dependency graph available here: https://zenodo.org/records/10605656
-If you use the dataset dump present in Zenodo, please use a Neo4j version 4.x.
+This graph was created by the Goblin Miner, and can be updated by it: https://github.com/Goblin-Ecosystem/goblinMiner
+
+An example of usage of this tool is available here: https://github.com/Goblin-Ecosystem/mavenDatasetExperiences (on Weaver version 1.0.0).
+
+If you use the dataset dump present in Zenodo, please use a Neo4j database version 4.x.
## Added values
- CVE: We use the osv.dev dataset to get CVEs information (https://osv.dev/).
- CVE_AGGREGATED: Aggregate release and dependencies (with transitivity) CVE.
- FRESHNESS: Corresponds, for a specific release, to the number of more recent releases available and to the time elapsed in milliseconds between the specific release and the most recent release.
- FRESHNESS_AGGREGATED: Aggregate release and dependencies (with transitivity) freshness.
+- POPULARITY_1_YEAR: Corresponds, for a specific release, the number of version released within a maximum of one year after the current graph date using the specified release.
+- POPULARITY_1_YEAR_AGGREGATED: Aggregate release and dependencies (with transitivity) POPULARITY_1_YEAR.
- SPEED: Corresponds to the average number of releases per day of an artifact. More information here: https://benevol2022.github.io/papers/DamienJaime.pdf
+## Memoization
+To avoid having to calculate the same metrics several times, added values are stored in the database graph once calculated.
+These new "AddedValue" type nodes are linked with "Release" or "Artifact" nodes.
+Due to their changing nature, routes are available in the API to remove them.
+However, the Weaver itself can delete them on change: when updating the CVE database, the "CVE" added values are deleted, when updating the database "FRESHNESS", "POPULARITY" and "SPEED" added values are deleted.
+
## Requirements
- Java 17
- Maven, with MAVEN_HOME defines
@@ -31,10 +41,10 @@ The program will first download the osv.dev dataset and create a folder called "
If you already have downloaded this dataset and you don't want to update it, you can add the "noUpdate" argument on the java -jar command.
Example:
-> java -Dneo4jUri="bolt://localhost:7687/" -Dneo4jUser="neo4j" -Dneo4jPassword="Password1" -jar .\target\goblinWeaver-1.0.0.jar
+> java -Dneo4jUri="bolt://localhost:7687/" -Dneo4jUser="neo4j" -Dneo4jPassword="Password1" -jar .\target\goblinWeaver-2.0.0.jar
-> java -Dneo4jUri="bolt://localhost:7687/" -Dneo4jUser="neo4j" -Dneo4jPassword="Password1" -jar .\target\goblinWeaver-1.0.0.jar
+> java -Dneo4jUri="bolt://localhost:7687/" -Dneo4jUser="neo4j" -Dneo4jPassword="Password1" -jar .\target\goblinWeaver-2.0.0.jar noUpdate
## Use the API
Pre-designed requests are available, but you can also send your own Cypher requests directly to the API.
@@ -47,9 +57,10 @@ A swagger documentation of the API is available here:
The Weaver is designed to be extensible, allowing a user to easily add information their research need.
Here's how to add an added value:
1. Go to weaver/addedValue/AddedValueEnum and add the name of your new value.
-2. Create a new class that implements weaver/addedValue/AddedValue.
-3. Write your internal logic in this new class.
-4. Go to weaver/Weaver and modify the "getValuesToAdd" method to define which type of element your new value can be applied to (Release, Artifact, dependency edge or relationship_AR edges).
+2. Fill the three methods of this enumeration with your new added value
+3. Create a new class that extends weaver/addedValue/AbstractAddedValue.
+4. (optional) If you also want to create an aggregated value of your new added value, create a new class that extends your previous new class and implements the "AggregateValue" interface.
+5. Write your internal logic in this new class.
## Licensing
-Copyright 2023 SAP SE or an SAP affiliate company and Neo4j Ecosystem Weaver. Please see our [LICENSE](LICENSE) for copyright and license information.
+Copyright 2024 SAP SE or an SAP affiliate company and Neo4j Ecosystem Weaver. Please see our [LICENSE](LICENSE) for copyright and license information.
diff --git a/src/main/java/com/cifre/sap/su/goblinWeaver/Neo4jEcosystemWeaverApplication.java b/src/main/java/com/cifre/sap/su/goblinWeaver/Neo4jEcosystemWeaverApplication.java
new file mode 100644
index 0000000..eab5bf4
--- /dev/null
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/Neo4jEcosystemWeaverApplication.java
@@ -0,0 +1,19 @@
+package com.cifre.sap.su.goblinWeaver;
+
+import com.cifre.sap.su.goblinWeaver.graphDatabase.GraphDatabaseSingleton;
+import com.cifre.sap.su.goblinWeaver.utils.GraphUpdatedChecker;
+import com.cifre.sap.su.goblinWeaver.utils.OsvProceeding;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Neo4jEcosystemWeaverApplication {
+
+ public static void main(String[] args) {
+ GraphDatabaseSingleton.getInstance(); // Init database connection
+ GraphUpdatedChecker.deleteAddedValuesIfUpdated(); // Check if database was updated
+ OsvProceeding.initOsvData(args); // Download CVE dataset
+ SpringApplication.run(Neo4jEcosystemWeaverApplication.class, args); // Run API
+ }
+
+}
diff --git a/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/ArtifactController.java b/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/ArtifactController.java
new file mode 100644
index 0000000..a38734c
--- /dev/null
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/ArtifactController.java
@@ -0,0 +1,42 @@
+package com.cifre.sap.su.goblinWeaver.api.controllers;
+
+import com.cifre.sap.su.goblinWeaver.api.entities.ArtifactQuery;
+import com.cifre.sap.su.goblinWeaver.graphDatabase.GraphDatabaseInterface;
+import com.cifre.sap.su.goblinWeaver.graphDatabase.GraphDatabaseSingleton;
+import com.cifre.sap.su.goblinWeaver.graphEntities.InternGraph;
+import com.cifre.sap.su.goblinWeaver.weaver.Weaver;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.json.simple.JSONObject;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@Tag(name = "Artifacts")
+public class ArtifactController {
+
+ @Operation(
+ description = "Get a specific artifact from groupId:ArtifactId with added values",
+ summary = "Get a specific artifact from GA"
+ )
+ @PostMapping("/artifact")
+ public JSONObject getSpecificArtifact(@RequestBody ArtifactQuery artifactQuery) {
+ GraphDatabaseInterface gdb = GraphDatabaseSingleton.getInstance();
+ InternGraph graph = gdb.executeQuery(gdb.getQueryDictionary().getSpecificArtifactQuery(artifactQuery.toString()));
+ Weaver.weaveGraph(graph, artifactQuery.getAddedValues());
+ return graph.getJsonGraph();
+ }
+
+ @Operation(
+ description = "Get all releases of an artifact from groupId:ArtifactId with added values",
+ summary = "Get all releases of an artifact from GA"
+ )
+ @PostMapping("/artifact/releases")
+ public JSONObject getArtifactReleases(@RequestBody ArtifactQuery artifactQuery) {
+ GraphDatabaseInterface gdb = GraphDatabaseSingleton.getInstance();
+ InternGraph graph = gdb.executeQuery(gdb.getQueryDictionary().getArtifactReleasesQuery(artifactQuery.toString()));
+ Weaver.weaveGraph(graph, artifactQuery.getAddedValues());
+ return graph.getJsonGraph();
+ }
+}
diff --git a/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/CypherController.java b/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/CypherController.java
new file mode 100644
index 0000000..0517c2b
--- /dev/null
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/CypherController.java
@@ -0,0 +1,26 @@
+package com.cifre.sap.su.goblinWeaver.api.controllers;
+
+import com.cifre.sap.su.goblinWeaver.api.entities.CypherQuery;
+import com.cifre.sap.su.goblinWeaver.graphDatabase.GraphDatabaseSingleton;
+import com.cifre.sap.su.goblinWeaver.graphEntities.InternGraph;
+import com.cifre.sap.su.goblinWeaver.weaver.Weaver;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.json.simple.JSONObject;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@Tag(name = "Cypher query")
+public class CypherController {
+
+ @Operation(
+ description = "Execute a cypher query to the dependency graph with added values",
+ summary = "Execute a cypher query"
+ )
+ @PostMapping("/cypher")
+ public JSONObject executeCypherQuery(@RequestBody CypherQuery queryRequest) {
+ InternGraph graph = GraphDatabaseSingleton.getInstance().executeQuery(queryRequest.getQuery());
+ Weaver.weaveGraph(graph, queryRequest.getAddedValues());
+ return graph.getJsonGraph();
+ }
+}
diff --git a/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/GraphController.java b/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/GraphController.java
new file mode 100644
index 0000000..e5203e6
--- /dev/null
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/GraphController.java
@@ -0,0 +1,161 @@
+package com.cifre.sap.su.goblinWeaver.api.controllers;
+
+import com.cifre.sap.su.goblinWeaver.api.entities.ReleaseQueryList;
+import com.cifre.sap.su.goblinWeaver.graphDatabase.GraphDatabaseSingleton;
+import com.cifre.sap.su.goblinWeaver.graphEntities.InternGraph;
+import com.cifre.sap.su.goblinWeaver.graphEntities.edges.DependencyEdge;
+import com.cifre.sap.su.goblinWeaver.graphEntities.nodes.NodeObject;
+import com.cifre.sap.su.goblinWeaver.graphEntities.nodes.NodeType;
+import com.cifre.sap.su.goblinWeaver.graphEntities.nodes.ReleaseNode;
+import com.cifre.sap.su.goblinWeaver.weaver.Weaver;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.json.simple.JSONObject;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@RestController
+@Tag(name = "Graph")
+public class GraphController {
+
+ @Operation(
+ description = "Get the project rooted graph",
+ summary = "Get the project rooted all graph from releases dependencies list"
+ )
+ @PostMapping("/graph/rootedGraph")
+ public JSONObject getRootedGraph(@RequestBody ReleaseQueryList releaseQueryList) {
+ InternGraph resultGraph = new InternGraph();
+ resultGraph.addNode(new ReleaseNode("ROOT", "ROOT", 0, ""));
+ for (ReleaseQueryList.Release release : releaseQueryList.getReleases()) {
+ resultGraph.addEdge(new DependencyEdge("ROOT", release.getGa(), release.getVersion(), "compile"));
+ }
+ resultGraph.mergeGraph(
+ GraphDatabaseSingleton.getInstance()
+ .getRootedGraph(
+ releaseQueryList.getReleases().stream().map(ReleaseQueryList.Release::getGav).collect(Collectors.toSet()
+ )
+ )
+ );
+ Weaver.weaveGraph(resultGraph, releaseQueryList.getAddedValues());
+ return resultGraph.getJsonGraph();
+ }
+
+ @Operation(
+ description = "Get the project rooted all possibilities graph",
+ summary = "Get the project rooted all possibilities graph from releases dependencies list"
+ )
+ @PostMapping("/graph/allPossibilitiesRooted")
+ public JSONObject getAllPossibilitiesRootedGraph(@RequestBody ReleaseQueryList releaseQueryList) {
+ InternGraph resultGraph = new InternGraph();
+ resultGraph.addNode(new ReleaseNode("ROOT", "ROOT", 0, ""));
+ for (ReleaseQueryList.Release release : releaseQueryList.getReleases()) {
+ resultGraph.addEdge(new DependencyEdge("ROOT", release.getGa(), release.getVersion(), "compile"));
+ }
+ resultGraph.mergeGraph(
+ GraphDatabaseSingleton.getInstance()
+ .getAllPossibilitiesGraph(
+ releaseQueryList.getReleases().stream().map(ReleaseQueryList.Release::getGa).collect(Collectors.toSet()
+ )
+ )
+ );
+ Weaver.weaveGraph(resultGraph, releaseQueryList.getAddedValues());
+ return resultGraph.getJsonGraph();
+ }
+
+ @Operation(
+ description = "Get the project rooted direct dependencies possibilities graph",
+ summary = "Get the project rooted direct dependencies possibilities graph from releases dependencies list"
+ )
+ @PostMapping("/graph/directPossibilitiesRooted")
+ public JSONObject getDirectPossibilitiesRootedGraph(@RequestBody ReleaseQueryList releaseQueryList) {
+ InternGraph resultGraph = new InternGraph();
+ resultGraph.addNode(new ReleaseNode("ROOT", "ROOT", 0, ""));
+ for (ReleaseQueryList.Release release : releaseQueryList.getReleases()) {
+ resultGraph.addEdge(new DependencyEdge("ROOT", release.getGa(), release.getVersion(), "compile"));
+ }
+ resultGraph.mergeGraph(
+ GraphDatabaseSingleton.getInstance()
+ .getDirectPossibilitiesGraph(
+ releaseQueryList.getReleases().stream().map(ReleaseQueryList.Release::getGa).collect(Collectors.toSet()
+ )
+ )
+ );
+ Weaver.weaveGraph(resultGraph, releaseQueryList.getAddedValues());
+ return resultGraph.getJsonGraph();
+ }
+
+ @Operation(
+ description = "Get the project rooted direct dependencies possibilities graph with transitive",
+ summary = "Get the project rooted direct dependencies possibilities graph with transitive version from releases dependencies list"
+ )
+ @PostMapping("/graph/directPossibilitiesWithTransitiveRooted")
+ public JSONObject getDirectPossibilitiesWithTransitiveRootedGraph(@RequestBody ReleaseQueryList releaseQueryList) {
+ InternGraph resultGraph = new InternGraph();
+ resultGraph.addNode(new ReleaseNode("ROOT", "ROOT", 0, ""));
+ for (ReleaseQueryList.Release release : releaseQueryList.getReleases()) {
+ resultGraph.addEdge(new DependencyEdge("ROOT", release.getGa(), release.getVersion(), "compile"));
+ }
+ // Get direct all possibilities
+ InternGraph directAllPossibilities = GraphDatabaseSingleton.getInstance()
+ .getDirectPossibilitiesGraph(releaseQueryList.getReleases().stream().map(ReleaseQueryList.Release::getGa).collect(Collectors.toSet()));
+ resultGraph.mergeGraph(directAllPossibilities);
+ // Get Releases dependencies
+ Map parameters = new HashMap<>();
+ Set visitedReleases = new HashSet<>();
+ Set releasesToTreat = directAllPossibilities.getGraphNodes().stream().filter(n -> n.getType().equals(NodeType.RELEASE)).map(NodeObject::getId).collect(Collectors.toSet());
+ while (!releasesToTreat.isEmpty()){
+ parameters.put("releaseIdList",releasesToTreat);
+ InternGraph queryResult = GraphDatabaseSingleton.getInstance().executeQueryWithParameters(GraphDatabaseSingleton.getInstance().getQueryDictionary().getDependencyGraphFromReleaseIdListParameter(), parameters);
+ resultGraph.mergeGraph(queryResult);
+ visitedReleases.addAll(releasesToTreat);
+ Set newReleaseToTreat = resultGraph.getGraphNodes().stream().filter(node -> node instanceof ReleaseNode).map(NodeObject::getId).collect(Collectors.toSet());
+ newReleaseToTreat.removeAll(visitedReleases);
+ releasesToTreat.clear();
+ releasesToTreat.addAll(newReleaseToTreat);
+ }
+
+ Weaver.weaveGraph(resultGraph, releaseQueryList.getAddedValues());
+ return resultGraph.getJsonGraph();
+ }
+
+ @Operation(
+ description = "Get the project rooted direct dependencies possibilities graph with transitive",
+ summary = "Get the project rooted direct dependencies possibilities graph with transitive version from releases dependencies list"
+ )
+ @PostMapping("/graph/directNewPossibilitiesWithTransitiveRooted")
+ public JSONObject getDirectNewPossibilitiesWithTransitiveRootedGraph(@RequestBody ReleaseQueryList releaseQueryList) {
+ InternGraph resultGraph = new InternGraph();
+ resultGraph.addNode(new ReleaseNode("ROOT", "ROOT", 0, ""));
+ for (ReleaseQueryList.Release release : releaseQueryList.getReleases()) {
+ resultGraph.addEdge(new DependencyEdge("ROOT", release.getGa(), release.getVersion(), "compile"));
+ }
+ // Get direct all possibilities
+ InternGraph directAllPossibilities = GraphDatabaseSingleton.getInstance()
+ .getDirectNewPossibilitiesGraph(releaseQueryList.getReleases());
+ resultGraph.mergeGraph(directAllPossibilities);
+ // Get Releases dependencies
+ Map parameters = new HashMap<>();
+ Set visitedReleases = new HashSet<>();
+ Set releasesToTreat = directAllPossibilities.getGraphNodes().stream().filter(n -> n.getType().equals(NodeType.RELEASE)).map(NodeObject::getId).collect(Collectors.toSet());
+ while (!releasesToTreat.isEmpty()){
+ parameters.put("releaseIdList",releasesToTreat);
+ InternGraph queryResult = GraphDatabaseSingleton.getInstance().executeQueryWithParameters(GraphDatabaseSingleton.getInstance().getQueryDictionary().getDependencyGraphFromReleaseIdListParameter(), parameters);
+ resultGraph.mergeGraph(queryResult);
+ visitedReleases.addAll(releasesToTreat);
+ Set newReleaseToTreat = resultGraph.getGraphNodes().stream().filter(node -> node instanceof ReleaseNode).map(NodeObject::getId).collect(Collectors.toSet());
+ newReleaseToTreat.removeAll(visitedReleases);
+ releasesToTreat.clear();
+ releasesToTreat.addAll(newReleaseToTreat);
+ }
+
+ Weaver.weaveGraph(resultGraph, releaseQueryList.getAddedValues());
+ return resultGraph.getJsonGraph();
+ }
+}
diff --git a/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/ReleaseController.java b/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/ReleaseController.java
new file mode 100644
index 0000000..0d09e2b
--- /dev/null
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/ReleaseController.java
@@ -0,0 +1,54 @@
+package com.cifre.sap.su.goblinWeaver.api.controllers;
+
+import com.cifre.sap.su.goblinWeaver.api.entities.ReleaseQuery;
+import com.cifre.sap.su.goblinWeaver.graphDatabase.GraphDatabaseInterface;
+import com.cifre.sap.su.goblinWeaver.graphDatabase.GraphDatabaseSingleton;
+import com.cifre.sap.su.goblinWeaver.graphEntities.InternGraph;
+import com.cifre.sap.su.goblinWeaver.weaver.Weaver;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.json.simple.JSONObject;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@Tag(name = "Releases")
+public class ReleaseController {
+
+ @Operation(
+ description = "Get a specific release from groupId:ArtifactId:Version with added values",
+ summary = "Get a specific release from GAV"
+ )
+ @PostMapping("/release")
+ public JSONObject getSpecificRelease(@RequestBody ReleaseQuery releaseQuery) {
+ GraphDatabaseInterface gdb = GraphDatabaseSingleton.getInstance();
+ InternGraph graph = gdb.executeQuery(gdb.getQueryDictionary().getSpecificRelease(releaseQuery.toString()));
+ Weaver.weaveGraph(graph, releaseQuery.getAddedValues());
+ return graph.getJsonGraph();
+ }
+
+ @Operation(
+ description = "Get newer versions of a release from groupId:ArtifactId:Version with added values",
+ summary = "Get newer versions of a release from GAV"
+ )
+ @PostMapping("/release/newVersions")
+ public JSONObject getNewerReleases(@RequestBody ReleaseQuery releaseQuery) {
+ GraphDatabaseInterface gdb = GraphDatabaseSingleton.getInstance();
+ InternGraph graph = gdb.executeQuery(gdb.getQueryDictionary().getNewerReleases(releaseQuery.toString(), releaseQuery.getGa()));
+ Weaver.weaveGraph(graph, releaseQuery.getAddedValues());
+ return graph.getJsonGraph();
+ }
+
+ @Operation(
+ description = "Get dependents of a release from groupId:ArtifactId:Version with added values",
+ summary = "Get release dependents from GAV"
+ )
+ @PostMapping("/release/dependents")
+ public JSONObject getReleaseDependent(@RequestBody ReleaseQuery releaseQuery) {
+ GraphDatabaseInterface gdb = GraphDatabaseSingleton.getInstance();
+ InternGraph graph = gdb.executeQuery(gdb.getQueryDictionary().getReleaseDependent(releaseQuery.getGa(), releaseQuery.getVersion()));
+ Weaver.weaveGraph(graph, releaseQuery.getAddedValues());
+ return graph.getJsonGraph();
+ }
+}
diff --git a/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/UtilsController.java b/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/UtilsController.java
new file mode 100644
index 0000000..a306460
--- /dev/null
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/api/controllers/UtilsController.java
@@ -0,0 +1,46 @@
+package com.cifre.sap.su.goblinWeaver.api.controllers;
+
+import com.cifre.sap.su.goblinWeaver.api.entities.AddedValueQuery;
+import com.cifre.sap.su.goblinWeaver.graphDatabase.GraphDatabaseSingleton;
+import com.cifre.sap.su.goblinWeaver.weaver.addedValue.AddedValueEnum;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.EnumSet;
+
+
+@RestController
+@Tag(name = "Utils")
+public class UtilsController {
+
+ @Operation(
+ description = "Get the list of existing added values on the Weaver",
+ summary = "Get existing added values"
+ )
+ @GetMapping("/addedValues")
+ public AddedValueEnum[] getAddedValue() {
+ return AddedValueEnum.values();
+ }
+
+ @Operation(
+ description = "Delete all added values nodes on database",
+ summary = "Delete all added values"
+ )
+ @DeleteMapping("/addedValues")
+ public void deleteAddedValues(){
+ GraphDatabaseSingleton.getInstance().removeAddedValuesOnGraph(EnumSet.allOf(AddedValueEnum.class));
+ }
+
+ @Operation(
+ description = "Delete specific added values nodes on database",
+ summary = "Delete specific added values"
+ )
+ @DeleteMapping("/addedValue")
+ public void deleteAddedValue(@RequestBody AddedValueQuery addedValueQuery){
+ GraphDatabaseSingleton.getInstance().removeAddedValuesOnGraph(addedValueQuery.getAddedValues());
+ }
+}
diff --git a/src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/AddedValueQuery.java b/src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/AddedValueQuery.java
new file mode 100644
index 0000000..9d27b5b
--- /dev/null
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/AddedValueQuery.java
@@ -0,0 +1,17 @@
+package com.cifre.sap.su.goblinWeaver.api.entities;
+
+import com.cifre.sap.su.goblinWeaver.weaver.addedValue.AddedValueEnum;
+
+import java.util.Set;
+
+public class AddedValueQuery {
+ private Set addedValues;
+
+ public Set getAddedValues() {
+ return addedValues;
+ }
+
+ public void setAddedValues(Set addedValues) {
+ this.addedValues = addedValues;
+ }
+}
diff --git a/src/main/java/com/cifre/sap/su/neo4jEcosystemWeaver/api/entities/ArtifactQuery.java b/src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/ArtifactQuery.java
similarity index 64%
rename from src/main/java/com/cifre/sap/su/neo4jEcosystemWeaver/api/entities/ArtifactQuery.java
rename to src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/ArtifactQuery.java
index 31496eb..cc1f45e 100644
--- a/src/main/java/com/cifre/sap/su/neo4jEcosystemWeaver/api/entities/ArtifactQuery.java
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/ArtifactQuery.java
@@ -1,13 +1,13 @@
-package com.cifre.sap.su.neo4jEcosystemWeaver.api.entities;
+package com.cifre.sap.su.goblinWeaver.api.entities;
-import com.cifre.sap.su.neo4jEcosystemWeaver.weaver.addedValue.AddedValueEnum;
+import com.cifre.sap.su.goblinWeaver.weaver.addedValue.AddedValueEnum;
-import java.util.List;
+import java.util.Set;
public class ArtifactQuery {
private String groupId;
private String artifactId;
- private List addedValues;
+ private Set addedValues;
public String getGroupId() {
return groupId;
@@ -25,11 +25,11 @@ public void setArtifactId(String artifactId) {
this.artifactId = artifactId;
}
- public List getAddedValues() {
+ public Set getAddedValues() {
return addedValues;
}
- public void setAddedValues(List addedValues) {
+ public void setAddedValues(Set addedValues) {
this.addedValues = addedValues;
}
diff --git a/src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/CypherQuery.java b/src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/CypherQuery.java
new file mode 100644
index 0000000..62be95d
--- /dev/null
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/CypherQuery.java
@@ -0,0 +1,27 @@
+package com.cifre.sap.su.goblinWeaver.api.entities;
+
+import com.cifre.sap.su.goblinWeaver.weaver.addedValue.AddedValueEnum;
+
+import java.util.Set;
+
+public class CypherQuery {
+ private String query;
+ private Set addedValues;
+
+ public String getQuery() {
+ return query;
+ }
+
+ public void setQuery(String query) {
+ this.query = query;
+ }
+
+ public Set getAddedValues() {
+ return addedValues;
+ }
+
+ public void setAddedValues(Set addedValues) {
+ this.addedValues = addedValues;
+ }
+}
+
diff --git a/src/main/java/com/cifre/sap/su/neo4jEcosystemWeaver/api/entities/ReleaseQuery.java b/src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/ReleaseQuery.java
similarity index 69%
rename from src/main/java/com/cifre/sap/su/neo4jEcosystemWeaver/api/entities/ReleaseQuery.java
rename to src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/ReleaseQuery.java
index 4fccfd8..f5e33dc 100644
--- a/src/main/java/com/cifre/sap/su/neo4jEcosystemWeaver/api/entities/ReleaseQuery.java
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/ReleaseQuery.java
@@ -1,14 +1,15 @@
-package com.cifre.sap.su.neo4jEcosystemWeaver.api.entities;
+package com.cifre.sap.su.goblinWeaver.api.entities;
-import com.cifre.sap.su.neo4jEcosystemWeaver.weaver.addedValue.AddedValueEnum;
+import com.cifre.sap.su.goblinWeaver.weaver.addedValue.AddedValueEnum;
+import io.swagger.v3.oas.annotations.Hidden;
-import java.util.List;
+import java.util.Set;
public class ReleaseQuery {
private String groupId;
private String artifactId;
private String version;
- private List addedValues;
+ private Set addedValues;
public String getGroupId() {
return groupId;
@@ -34,14 +35,15 @@ public void setVersion(String version) {
this.version = version;
}
- public List getAddedValues() {
+ public Set getAddedValues() {
return addedValues;
}
- public void setAddedValues(List addedValues) {
+ public void setAddedValues(Set addedValues) {
this.addedValues = addedValues;
}
+ @Hidden
public String getGa(){
return groupId+":"+artifactId;
}
diff --git a/src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/ReleaseQueryList.java b/src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/ReleaseQueryList.java
new file mode 100644
index 0000000..416dc8c
--- /dev/null
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/api/entities/ReleaseQueryList.java
@@ -0,0 +1,67 @@
+package com.cifre.sap.su.goblinWeaver.api.entities;
+
+import com.cifre.sap.su.goblinWeaver.weaver.addedValue.AddedValueEnum;
+import io.swagger.v3.oas.annotations.Hidden;
+
+import java.util.Set;
+
+public class ReleaseQueryList {
+ Set releases;
+ private Set addedValues;
+
+ public Set getReleases() {
+ return releases;
+ }
+
+ public void setReleases(Set releases) {
+ this.releases = releases;
+ }
+
+ public Set getAddedValues() {
+ return addedValues;
+ }
+
+ public void setAddedValues(Set addedValues) {
+ this.addedValues = addedValues;
+ }
+
+ public static class Release{
+ private String groupId;
+ private String artifactId;
+ private String version;
+
+ public String getGroupId() {
+ return groupId;
+ }
+
+ public void setGroupId(String groupId) {
+ this.groupId = groupId;
+ }
+
+ public String getArtifactId() {
+ return artifactId;
+ }
+
+ public void setArtifactId(String artifactId) {
+ this.artifactId = artifactId;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+
+ @Hidden
+ public String getGa(){
+ return groupId+":"+artifactId;
+ }
+
+ @Hidden
+ public String getGav(){
+ return groupId+":"+artifactId+":"+version;
+ }
+ }
+}
diff --git a/src/main/java/com/cifre/sap/su/goblinWeaver/graphDatabase/GraphDatabaseInterface.java b/src/main/java/com/cifre/sap/su/goblinWeaver/graphDatabase/GraphDatabaseInterface.java
new file mode 100644
index 0000000..ba57a9b
--- /dev/null
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/graphDatabase/GraphDatabaseInterface.java
@@ -0,0 +1,25 @@
+package com.cifre.sap.su.goblinWeaver.graphDatabase;
+
+import com.cifre.sap.su.goblinWeaver.api.entities.ReleaseQueryList;
+import com.cifre.sap.su.goblinWeaver.graphEntities.InternGraph;
+import com.cifre.sap.su.goblinWeaver.graphEntities.nodes.NodeType;
+import com.cifre.sap.su.goblinWeaver.weaver.addedValue.AddedValue;
+import com.cifre.sap.su.goblinWeaver.weaver.addedValue.AddedValueEnum;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public interface GraphDatabaseInterface {
+ QueryDictionary getQueryDictionary();
+ InternGraph executeQuery(String query);
+ InternGraph executeQueryWithParameters(String query, Map parameters);
+ Map> getNodeAddedValues(List nodeIds, Set addedValues, NodeType nodeType);
+ void addAddedValues(List> computedAddedValues);
+ void putOneAddedValueOnGraph(String nodeId, AddedValueEnum addedValueType, String value);
+ void removeAddedValuesOnGraph(Set addedValuesType);
+ InternGraph getRootedGraph(Set artifactIdList);
+ InternGraph getAllPossibilitiesGraph(Set artifactIdList);
+ InternGraph getDirectPossibilitiesGraph(Set artifactIdList);
+ InternGraph getDirectNewPossibilitiesGraph(Set artifactIdList);
+}
diff --git a/src/main/java/com/cifre/sap/su/goblinWeaver/graphDatabase/GraphDatabaseSingleton.java b/src/main/java/com/cifre/sap/su/goblinWeaver/graphDatabase/GraphDatabaseSingleton.java
new file mode 100644
index 0000000..17c6d7b
--- /dev/null
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/graphDatabase/GraphDatabaseSingleton.java
@@ -0,0 +1,27 @@
+package com.cifre.sap.su.goblinWeaver.graphDatabase;
+
+import com.cifre.sap.su.goblinWeaver.graphDatabase.neo4j.Neo4jGraphDatabase;
+
+public class GraphDatabaseSingleton {
+
+ private static GraphDatabaseInterface graphDatabase;
+
+ private GraphDatabaseSingleton() {
+ // private constructor to prevent instantiation
+ }
+
+ public static GraphDatabaseInterface getInstance() {
+ if (graphDatabase == null) {
+ synchronized (GraphDatabaseInterface.class) {
+ //TODO: To be changed if you want to add a new graph database
+ if (graphDatabase == null) {
+ String uri = System.getProperty("neo4jUri");
+ String user = System.getProperty("neo4jUser");
+ String password = System.getProperty("neo4jPassword");
+ graphDatabase = new Neo4jGraphDatabase(uri, user, password);
+ }
+ }
+ }
+ return graphDatabase;
+ }
+}
diff --git a/src/main/java/com/cifre/sap/su/goblinWeaver/graphDatabase/QueryDictionary.java b/src/main/java/com/cifre/sap/su/goblinWeaver/graphDatabase/QueryDictionary.java
new file mode 100644
index 0000000..d2cb7ca
--- /dev/null
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/graphDatabase/QueryDictionary.java
@@ -0,0 +1,17 @@
+package com.cifre.sap.su.goblinWeaver.graphDatabase;
+
+public interface QueryDictionary {
+ String getSpecificArtifactQuery(String artifactId);
+ String getArtifactReleasesQuery(String artifactId);
+ String getSpecificRelease(String releaseId);
+ String getReleaseDependent(String artifactId, String releaseVersion);
+ String getNewerReleases(String releaseId, String artifactId);
+ String getReleaseFreshness(String releaseId);
+ String getReleasePopularity1Year(String artifactGa, String releaseVersion);
+ String getArtifactRhythm(String artifactId);
+ String getReleaseDirectCompileDependencies(String artifactId);
+ String getLinkedArtifactReleasesAndEdgesQuery(String artifactId);
+ String getReleaseDirectCompileDependenciesEdgeAndArtifact(String artifactId);
+ String getLastReleaseTimestamp();
+ String getDependencyGraphFromReleaseIdListParameter();
+}
diff --git a/src/main/java/com/cifre/sap/su/goblinWeaver/graphDatabase/neo4j/Neo4jGraphDatabase.java b/src/main/java/com/cifre/sap/su/goblinWeaver/graphDatabase/neo4j/Neo4jGraphDatabase.java
new file mode 100644
index 0000000..61f17fc
--- /dev/null
+++ b/src/main/java/com/cifre/sap/su/goblinWeaver/graphDatabase/neo4j/Neo4jGraphDatabase.java
@@ -0,0 +1,314 @@
+package com.cifre.sap.su.goblinWeaver.graphDatabase.neo4j;
+
+import com.cifre.sap.su.goblinWeaver.api.entities.ReleaseQueryList;
+import com.cifre.sap.su.goblinWeaver.graphDatabase.GraphDatabaseInterface;
+import com.cifre.sap.su.goblinWeaver.graphDatabase.QueryDictionary;
+import com.cifre.sap.su.goblinWeaver.graphEntities.GraphObject;
+import com.cifre.sap.su.goblinWeaver.graphEntities.InternGraph;
+import com.cifre.sap.su.goblinWeaver.graphEntities.ValueObject;
+import com.cifre.sap.su.goblinWeaver.graphEntities.edges.DependencyEdge;
+import com.cifre.sap.su.goblinWeaver.graphEntities.edges.EdgeObject;
+import com.cifre.sap.su.goblinWeaver.graphEntities.edges.EdgeType;
+import com.cifre.sap.su.goblinWeaver.graphEntities.edges.RelationshipArEdge;
+import com.cifre.sap.su.goblinWeaver.graphEntities.nodes.ArtifactNode;
+import com.cifre.sap.su.goblinWeaver.graphEntities.nodes.NodeObject;
+import com.cifre.sap.su.goblinWeaver.graphEntities.nodes.NodeType;
+import com.cifre.sap.su.goblinWeaver.graphEntities.nodes.ReleaseNode;
+import com.cifre.sap.su.goblinWeaver.weaver.addedValue.AddedValue;
+import com.cifre.sap.su.goblinWeaver.weaver.addedValue.AddedValueEnum;
+import org.neo4j.driver.*;
+import org.neo4j.driver.Record;
+import org.neo4j.driver.types.Node;
+import org.neo4j.driver.types.Path;
+import org.neo4j.driver.types.Relationship;
+import org.neo4j.driver.types.TypeSystem;
+import org.neo4j.driver.util.Pair;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+public class Neo4jGraphDatabase implements GraphDatabaseInterface {
+ private final Driver driver;
+ private final QueryDictionary queryDictionary = new Neo4jQueryDictionary();
+
+ public Neo4jGraphDatabase(String uri, String user, String password) {
+ driver = GraphDatabase.driver(uri, AuthTokens.basic(user, password));
+ //Init index for added values
+ try (Session session = driver.session()) {
+ session.run("CREATE CONSTRAINT addedValueConstraint IF NOT EXISTS FOR (n:AddedValue) REQUIRE n.id IS UNIQUE");
+ }
+ }
+
+ public QueryDictionary getQueryDictionary() {
+ return queryDictionary;
+ }
+
+ @Override
+ public InternGraph executeQuery(String query) {
+ try (Session session = driver.session()) {
+ return treatNeo4jResult(session.run(query));
+ }
+ }
+
+ @Override
+ public InternGraph executeQueryWithParameters(String query, Map parameters) {
+ try (Session session = driver.session()) {
+ return treatNeo4jResult(session.run(query, parameters));
+ }
+ }
+
+ private InternGraph treatNeo4jResult(Result result){
+ InternGraph graph = new InternGraph();
+ while (result.hasNext()) {
+ Record record = result.next();
+ for (Pair pair : record.fields()) {
+ if (pair.value().hasType(TypeSystem.getDefault().NODE())){
+ NodeObject nodeObject = generateNode(pair.value().asNode());
+ if(nodeObject != null){
+ graph.addNode(nodeObject);
+ }
+ }
+ else if (pair.value().hasType(TypeSystem.getDefault().RELATIONSHIP())){
+ EdgeObject edgeObject = generateRelationship(pair.value().asRelationship());
+ if(edgeObject != null){
+ graph.addEdge(edgeObject);
+ }
+ }
+ else if (pair.value().hasType(TypeSystem.getDefault().PATH())) {
+ for(GraphObject graphObject : generatePath(pair.value().asPath())){
+ if (graphObject instanceof NodeObject) {
+ graph.addNode((NodeObject) graphObject);
+ } else {
+ graph.addEdge((EdgeObject) graphObject);
+ }
+ }
+ }
+ else if (pair.value().hasType(TypeSystem.getDefault().LIST())){
+ List