From c0b5b8ad4222b65cc7dc5d46be14d3834b255d21 Mon Sep 17 00:00:00 2001
From: Jan-Niclas Struewer <j.n.struewer@gmail.com>
Date: Tue, 16 May 2023 09:06:45 +0200
Subject: [PATCH] added data persistence for sarif-based tools. Currently, not
 working due to lazy loading issue with toolRuns in repository db object.

---
 .../iem/dataprovider/gitlab/Repository.kt     | 15 ++++++-
 .../gitlab/RepositoryRepository.kt            |  6 +++
 .../dataprovider/taskManager/TaskManager.kt   | 44 ++++++++++++++-----
 .../taskManager/tasks/CloneGitTask.kt         | 26 ++++++++---
 .../taskManager/tasks/DetektTask.kt           | 34 ++++++--------
 .../taskManager/tasks/GitlabTask.kt           | 16 +++++--
 .../taskManager/tasks/ProcessTask.kt          | 40 +++++++++++++++++
 .../ToolResultsPersistenceService.kt          | 17 -------
 .../toolResult/ToolResultsRepository.kt       |  4 --
 .../iem/dataprovider/toolResult/ToolRun.kt    | 16 ++++---
 10 files changed, 147 insertions(+), 71 deletions(-)
 delete mode 100644 src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolResultsPersistenceService.kt
 delete mode 100644 src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolResultsRepository.kt

diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/gitlab/Repository.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/gitlab/Repository.kt
index 90e0d078..7fca2911 100644
--- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/gitlab/Repository.kt
+++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/gitlab/Repository.kt
@@ -14,6 +14,19 @@ class Repository {
     var id: UUID? = null
 
     @OrderBy("time_stamp DESC")
-    @OneToMany(cascade = [CascadeType.ALL], mappedBy = "repository")
+    @OneToMany(mappedBy = "repository", cascade = [CascadeType.ALL])
     var toolRuns: MutableList<ToolRun> = mutableListOf()
+
+    @Column(name = "name")
+    var name: String? = null
+
+    @Column(name = "repo_id")
+    var repoId: Long? = null
+
+    @Enumerated
+    @Column(name = "platform")
+    var platform: Platform? = null
+
+    @Column(name = "url")
+    var url: String? = null
 }
\ No newline at end of file
diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/gitlab/RepositoryRepository.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/gitlab/RepositoryRepository.kt
index 3e39deed..23fa8211 100644
--- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/gitlab/RepositoryRepository.kt
+++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/gitlab/RepositoryRepository.kt
@@ -4,4 +4,10 @@ import org.springframework.data.jpa.repository.JpaRepository
 import java.util.*
 
 interface RepositoryRepository : JpaRepository<Repository, UUID> {
+
+
+    fun existsByRepoIdAndPlatform(repoId: Long, platform: Platform): Boolean
+
+
+    fun findByRepoIdAndPlatform(repoId: Long, platform: Platform): Repository?
 }
\ No newline at end of file
diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/TaskManager.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/TaskManager.kt
index e09f769a..c6488980 100644
--- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/TaskManager.kt
+++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/TaskManager.kt
@@ -1,10 +1,14 @@
 package de.fraunhofer.iem.dataprovider.taskManager
 
 import de.fraunhofer.iem.dataprovider.gitlab.GitConfiguration
+import de.fraunhofer.iem.dataprovider.gitlab.RepositoryRepository
 import de.fraunhofer.iem.dataprovider.logger.getLogger
 import de.fraunhofer.iem.dataprovider.sarif.Sarif
-import de.fraunhofer.iem.dataprovider.taskManager.tasks.*
-import de.fraunhofer.iem.dataprovider.toolResult.ToolResultsPersistenceService
+import de.fraunhofer.iem.dataprovider.taskManager.tasks.CloneGitTask
+import de.fraunhofer.iem.dataprovider.taskManager.tasks.DetektTask
+import de.fraunhofer.iem.dataprovider.taskManager.tasks.GetGitlabProjectTask
+import de.fraunhofer.iem.dataprovider.taskManager.tasks.GitRepository
+import de.fraunhofer.iem.dataprovider.toolResult.ToolRunRepository
 import jakarta.annotation.PreDestroy
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -23,8 +27,8 @@ sealed class TaskDone : Event() {
     abstract val taskId: UUID
 }
 
-class GitCloneDone(override val taskId: UUID, val outputDirectory: String) : TaskDone()
-class GetGitlabProjectDone(override val taskId: UUID, val gitProject: GitProject) : TaskDone()
+class GitCloneDone(override val taskId: UUID, val repoId: UUID, val outputDirectory: String) : TaskDone()
+class GetGitlabProjectDone(override val taskId: UUID, val repoId: UUID, val gitRepository: GitRepository) : TaskDone()
 class ProcessTaskDone(override val taskId: UUID, val message: String) : TaskDone()
 
 class DetektDone(override val taskId: UUID, val sarif: Sarif) : TaskDone()
@@ -37,7 +41,8 @@ class DetektDone(override val taskId: UUID, val sarif: Sarif) : TaskDone()
 @Component
 class TaskManager(
     private val config: Config,
-    private val toolResultsPersistenceService: ToolResultsPersistenceService
+    private val repositoryRepository: RepositoryRepository,
+    private val toolRunRepository: ToolRunRepository
 ) {
 
     // The used default dispatcher is ok for CPU-bound workloads. However,
@@ -90,13 +95,22 @@ class TaskManager(
 
             when (event) {
                 is RepoChangedEvent -> {
-                    ioWorker.addTask(GetGitlabProjectTask(event.repoId, event.gitConfiguration, ::addEvent))
+                    ioWorker.addTask(
+                        GetGitlabProjectTask(
+                            event.repoId,
+                            event.gitConfiguration,
+                            ::addEvent,
+                            repositoryRepository
+                        )
+                    )
                 }
 
                 is GetGitlabProjectDone -> {
+
                     ioWorker.addTask(
                         CloneGitTask(
-                            event.gitProject,
+                            event.gitRepository,
+                            event.repoId,
                             ::addEvent,
                             config.gitProjectPath
                         )
@@ -106,13 +120,19 @@ class TaskManager(
                 is GitCloneDone -> {
                     // TODO: analysis results path should be unique or best not placed in the git folder
                     val analysisResultsPath = Paths.get(event.outputDirectory, "tool-results").toString()
-                    worker.addTask(OdcTask(event.outputDirectory, analysisResultsPath, ::addEvent))
-                    worker.addTask(DetektTask(event.outputDirectory, analysisResultsPath, ::addEvent))
+//                    worker.addTask(OdcTask(event.outputDirectory, analysisResultsPath, ::addEvent))
+                    worker.addTask(
+                        DetektTask(
+                            event.outputDirectory,
+                            analysisResultsPath,
+                            ::addEvent,
+                            event.repoId,
+                            repositoryRepository,
+                            toolRunRepository
+                        )
+                    )
                 }
 
-                is DetektDone -> {
-                    toolResultsPersistenceService.save(event.sarif)
-                }
 
                 else -> {
                     logger.info("Received event without special handling associated $event")
diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/CloneGitTask.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/CloneGitTask.kt
index 3cd1753c..187f4aeb 100644
--- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/CloneGitTask.kt
+++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/CloneGitTask.kt
@@ -1,33 +1,45 @@
 package de.fraunhofer.iem.dataprovider.taskManager.tasks
 
+import de.fraunhofer.iem.dataprovider.gitlab.Platform
+import de.fraunhofer.iem.dataprovider.gitlab.Repository
 import de.fraunhofer.iem.dataprovider.taskManager.Event
 import de.fraunhofer.iem.dataprovider.taskManager.GitCloneDone
 import org.eclipse.jgit.api.Git
 import java.nio.file.Paths
+import java.util.*
 
 
-data class GitProject(val name: String, val uri: String)
+data class GitRepository(val name: String, val uri: String, val id: Long, val platform: Platform)
+
+fun GitRepository.toDbObject(): Repository {
+    val repo = Repository()
+    repo.name = this.name
+    repo.repoId = this.id
+    repo.platform = this.platform
+    return repo
+}
 
 class CloneGitTask(
-    private val gitProject: GitProject,
+    private val gitRepository: GitRepository,
+    private val repoDbId: UUID,
     override val responseChannel: suspend (task: Event) -> Unit,
     private val outputPath: String,
 ) : Task() {
 
     override suspend fun execute() {
 
-        val outputDirName = "${gitProject.name}-${taskID}"
+        val outputDirName = "${gitRepository.name}-${taskID}"
         val outputDirectory = Paths.get(outputPath, outputDirName)
-        logger.info("Cloning ${gitProject.name} into $outputDirectory")
+        logger.info("Cloning ${gitRepository.name} into $outputDirectory")
 
         val git: Git = Git.cloneRepository()
-            .setURI(gitProject.uri)
+            .setURI(gitRepository.uri)
             .setDirectory(outputDirectory.toFile())
             .call()
         git.close()
 
-        responseChannel(GitCloneDone(taskID, outputDirectory.toString()))
+        responseChannel(GitCloneDone(taskID, repoDbId, outputDirectory.toString()))
 
-        logger.info("Finished cloning ${gitProject.name}")
+        logger.info("Finished cloning ${gitRepository.name}")
     }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/DetektTask.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/DetektTask.kt
index 1d464e8d..18d46f47 100644
--- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/DetektTask.kt
+++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/DetektTask.kt
@@ -1,38 +1,32 @@
 package de.fraunhofer.iem.dataprovider.taskManager.tasks
 
 
-import de.fraunhofer.iem.dataprovider.sarif.getSarifFromFilePath
+import de.fraunhofer.iem.dataprovider.gitlab.RepositoryRepository
+import de.fraunhofer.iem.dataprovider.sarif.Sarif
 import de.fraunhofer.iem.dataprovider.taskManager.DetektDone
 import de.fraunhofer.iem.dataprovider.taskManager.Event
+import de.fraunhofer.iem.dataprovider.toolResult.ToolRunRepository
 import org.springframework.core.io.ClassPathResource
 import org.springframework.core.io.Resource
+import java.nio.file.Path
 import java.nio.file.Paths
+import java.util.*
 
 
-class DetektTask(projectPath: String, outputPath: String, override val responseChannel: suspend (task: Event) -> Unit) :
-    ProcessTask() {
+class DetektTask(
+    projectPath: String, outputPath: String, override val responseChannel: suspend (task: Event) -> Unit,
+    override val repoId: UUID,
+    override val repository: RepositoryRepository,
+    override val toolRunRepository: ToolRunRepository
+) : SarifTask() {
 
     private val resource: Resource = ClassPathResource("scripts/detekt.sh")
     override val flags: Array<String> = arrayOf(resource.file.absolutePath, projectPath, outputPath)
-
     override val execPath: String = "/bin/sh"
+    override val resultPath: Path = Paths.get(outputPath, "detekt", "report.sarif")
 
-    private val resultPath = Paths.get(outputPath, "detekt", "report.sarif")
-    override suspend fun handleProcessReturn(p: Process) {
-        logger.info(resource.toString())
-
-        val returnMessage = "Odc finished with exit code ${p.exitValue()}"
-//        val output = String(p.inputStream.readAllBytes())
-//        logger.info("Process output $output")
-
-        val sarifResult = getSarifFromFilePath(resultPath)
-
-        sarifResult.runs.forEach {
-            it.results.forEach {
-                logger.info("run in sarif result message: ${it.message}")
-            }
-        }
 
-        responseChannel(DetektDone(this.taskID, sarifResult))
+    override suspend fun sendResult(sarif: Sarif) {
+        responseChannel(DetektDone(this.taskID, sarif))
     }
 }
\ No newline at end of file
diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/GitlabTask.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/GitlabTask.kt
index 7ac7a188..3a465cbe 100644
--- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/GitlabTask.kt
+++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/GitlabTask.kt
@@ -1,6 +1,8 @@
 package de.fraunhofer.iem.dataprovider.taskManager.tasks
 
 import de.fraunhofer.iem.dataprovider.gitlab.GitConfiguration
+import de.fraunhofer.iem.dataprovider.gitlab.Platform
+import de.fraunhofer.iem.dataprovider.gitlab.RepositoryRepository
 import de.fraunhofer.iem.dataprovider.taskManager.Event
 import de.fraunhofer.iem.dataprovider.taskManager.GetGitlabProjectDone
 import org.gitlab4j.api.GitLabApi
@@ -8,7 +10,8 @@ import org.gitlab4j.api.GitLabApi
 
 class GetGitlabProjectTask(
     private val repoId: Long, private val gitlabConfiguration: GitConfiguration,
-    override val responseChannel: suspend (task: Event) -> Unit
+    override val responseChannel: suspend (task: Event) -> Unit,
+    private val repository: RepositoryRepository
 ) : Task() {
 
 
@@ -20,10 +23,17 @@ class GetGitlabProjectTask(
         val project = gitlabApi.projectApi.getProject(repoId)
         logger.info(project.toString())
         val projectUri = project.httpUrlToRepo
-        val gitProject = GitProject(project.path, projectUri)
 
-        responseChannel(GetGitlabProjectDone(taskID, gitProject))
+        val gitRepository = GitRepository(project.path, projectUri, repoId, Platform.OPEN_CODE)
+
+        var repo = repository.findByRepoIdAndPlatform(gitRepository.id, gitRepository.platform)
+        if (repo == null) {
+            logger.info("Repository ${gitRepository.id} ${gitRepository.platform} doesn't exist in database.")
+            repo = repository.save(gitRepository.toDbObject())
+        }
 
         logger.info("Retrieved project ${project.path} and url $projectUri")
+
+        responseChannel(GetGitlabProjectDone(taskID, repo.id!!, gitRepository))
     }
 }
diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/ProcessTask.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/ProcessTask.kt
index 46c6a45a..b27c4c58 100644
--- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/ProcessTask.kt
+++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/ProcessTask.kt
@@ -1,10 +1,19 @@
 package de.fraunhofer.iem.dataprovider.taskManager.tasks
 
+import de.fraunhofer.iem.dataprovider.gitlab.RepositoryRepository
+import de.fraunhofer.iem.dataprovider.sarif.Sarif
+import de.fraunhofer.iem.dataprovider.sarif.asDbObject
+import de.fraunhofer.iem.dataprovider.sarif.getSarifFromFilePath
+import de.fraunhofer.iem.dataprovider.toolResult.ToolRunRepository
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
+import org.hibernate.Hibernate
+import org.springframework.transaction.annotation.Transactional
 import java.nio.file.Files
+import java.nio.file.Path
 import java.nio.file.Paths
+import java.util.*
 
 sealed class ProcessTask : Task() {
     protected abstract val flags: Array<String>
@@ -33,4 +42,35 @@ sealed class ProcessTask : Task() {
     }
 
     abstract suspend fun handleProcessReturn(p: Process)
+
+}
+
+sealed class SarifTask : ProcessTask() {
+    protected abstract val repoId: UUID
+    protected abstract val repository: RepositoryRepository
+    protected abstract val toolRunRepository: ToolRunRepository
+    protected abstract val resultPath: Path
+
+    @Transactional
+    override suspend fun handleProcessReturn(p: Process) {
+
+        // TODO: check process exit codes. Current problem, if an analysis
+        //  has findings it might return an exit code != 0 even tho the process finished correctly
+
+        logger.info("Handle Process return in $javaClass")
+        val sarifResult = getSarifFromFilePath(resultPath)
+        val repo = repository.findById(repoId)
+
+        val results = toolRunRepository.saveAll(sarifResult.asDbObject())
+
+        repo.ifPresent {
+            Hibernate.initialize(it.toolRuns) // TODO: this fails because the session seems to be closed and add won't work because toolRuns are loaded lazily
+            it.toolRuns.addAll(results)
+            repository.save(it)
+        }
+        sendResult(sarifResult)
+    }
+
+    abstract suspend fun sendResult(sarif: Sarif)
+
 }
diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolResultsPersistenceService.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolResultsPersistenceService.kt
deleted file mode 100644
index c828d707..00000000
--- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolResultsPersistenceService.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package de.fraunhofer.iem.dataprovider.toolResult
-
-import de.fraunhofer.iem.dataprovider.logger.getLogger
-import de.fraunhofer.iem.dataprovider.sarif.Sarif
-import org.springframework.stereotype.Service
-
-@Service
-class ToolResultsPersistenceService(val toolRunRepository: ToolRunRepository) {
-
-    private val logger = getLogger(javaClass)
-    suspend fun save(sarif: Sarif) {
-        logger.info("Save sarif request received.")
-
-        toolRunRepository.save(ToolRun())
-    }
-
-}
\ No newline at end of file
diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolResultsRepository.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolResultsRepository.kt
deleted file mode 100644
index e878abc9..00000000
--- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolResultsRepository.kt
+++ /dev/null
@@ -1,4 +0,0 @@
-package de.fraunhofer.iem.dataprovider.toolResult
-
-//interface ToolResultsRepository : CoroutineCrudRepository<ToolResultsDb, String> {
-//}
\ No newline at end of file
diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolRun.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolRun.kt
index d8ad7dbe..1c39b55d 100644
--- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolRun.kt
+++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolRun.kt
@@ -20,10 +20,10 @@ class ToolRun {
     @Column(name = "id", nullable = false)
     var id: UUID? = null
 
-    @OneToMany(mappedBy = "toolRun", orphanRemoval = true)
+    @OneToMany(mappedBy = "toolRun", orphanRemoval = true, cascade = [CascadeType.ALL])
     var toolResults: MutableSet<ToolResult> = mutableSetOf()
 
-    @OneToOne(orphanRemoval = true)
+    @OneToOne(orphanRemoval = true, cascade = [CascadeType.ALL])
     @JoinColumn(name = "tool_id")
     var tool: Tool? = null
 
@@ -44,10 +44,10 @@ class Tool {
     @Column(name = "id", nullable = false)
     var id: UUID? = null
 
-    @OneToMany(mappedBy = "tool", orphanRemoval = true)
+    @OneToMany(mappedBy = "tool", orphanRemoval = true, cascade = [CascadeType.ALL])
     var rules: MutableSet<Rule> = mutableSetOf()
 
-    @Column(name = "name")
+    @Column(name = "name", length = 500)
     var name: String? = null
 
     @Column(name = "version")
@@ -74,7 +74,7 @@ class ToolResult {
     @JoinColumn(name = "tool_run_id")
     var toolRun: ToolRun? = null
 
-    @OneToMany(mappedBy = "toolResult", orphanRemoval = true)
+    @OneToMany(mappedBy = "toolResult", cascade = [CascadeType.ALL], orphanRemoval = true)
     var resultLocations: MutableSet<ResultLocation> = mutableSetOf()
 
     @Enumerated
@@ -85,6 +85,7 @@ class ToolResult {
     @Column(name = "kind")
     var kind: Kind? = null
 
+    @Lob
     @Column(name = "message")
     var message: String? = null
 
@@ -115,6 +116,7 @@ class ResultLocation {
     @JoinColumn(name = "rule_id")
     var rule: Rule? = null
 
+    @Lob
     @Column(name = "artifact_location")
     var artifactLocation: String? = null
 
@@ -157,14 +159,14 @@ class Rule {
     @Column(name = "rule_id")
     var ruleId: String? = null
 
-
+    @Lob
     @Column(name = "short_description")
     var shortDescription: String? = null
 
 
     @ElementCollection
     @CollectionTable(name = "rule_messages", joinColumns = [JoinColumn(name = "owner_id")])
-    @Column(name = "message")
+    @Column(name = "message", length = 1000)
     var messages: MutableList<String> = mutableListOf()
 
     override fun equals(other: Any?): Boolean {
-- 
GitLab