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 fb409de39884cd3ec58f0a4f13d1c09b04502750..9a6ccf02f3ce3ea26bed52af260bc182048684c2 100644 --- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/gitlab/Repository.kt +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/gitlab/Repository.kt @@ -14,7 +14,11 @@ class Repository { var id: UUID? = null @OrderBy("time_stamp DESC") - @OneToMany(mappedBy = "repository", cascade = [CascadeType.ALL], orphanRemoval = true) + @OneToMany( + mappedBy = "repository", + cascade = [CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REMOVE, CascadeType.DETACH], + orphanRemoval = true + ) var toolRuns: MutableList<ToolRun> = mutableListOf() @Column(name = "name") diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/sarif/SarifExtensions.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/sarif/SarifExtensions.kt index b535c2e3c286bd0bf89226200130319ac14cc9d8..145f0cad7ad8545b389a712990dabeb69944bd33 100644 --- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/sarif/SarifExtensions.kt +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/sarif/SarifExtensions.kt @@ -21,7 +21,6 @@ fun de.fraunhofer.iem.dataprovider.sarif.Rule.asDbObject(): Rule { fun de.fraunhofer.iem.dataprovider.sarif.Tool.asDbObject(): Tool { val tool = Tool() - tool.addRules(this.driver.rules.map { it.asDbObject() }) tool.fullName = this.driver.fullName tool.name = this.driver.name tool.version = this.driver.version @@ -38,7 +37,7 @@ fun Location.asDbObject(): ResultLocation { return resultLocation } -fun Result.asDbObject(rules: Collection<Rule>): ToolResult { +fun Result.asDbObject(): ToolResult { val toolResult = ToolResult() when (this.level.uppercase(Locale.getDefault())) { @@ -48,26 +47,19 @@ fun Result.asDbObject(rules: Collection<Rule>): ToolResult { else -> toolResult.level = Level.None } - rules - .filter { it.sarifRuleId == this.ruleId } - .forEach { it.addResultLocation(toolResult) } - toolResult.message = this.message.text toolResult.addLocations(this.locations.map { it.asDbObject() }) return toolResult } -fun Sarif.asDbObject(): List<ToolRun> { - return this.runs.map { run -> - val tr = ToolRun() - tr.tool = run.tool.asDbObject() - tr.addToolResults(run.results.map { it.asDbObject(tr.tool!!.rules) }) - tr.timeStamp = Timestamp.from(Instant.now()) - tr - } +fun Run.asDbObject(): ToolRun { + val tr = ToolRun() + tr.timeStamp = Timestamp.from(Instant.now()) + return tr } + fun getSarifFromFilePath(resultPath: Path): Sarif { val resFile = resultPath.toFile() if (resFile.exists()) { 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 d2fda8fab65e3ee16c162e38024284fe3da534fd..33fab379845774c8cc4cdfdf83cae67cb2b0709e 100644 --- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/TaskManager.kt +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/TaskManager.kt @@ -5,7 +5,7 @@ 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.ToolRunRepository +import de.fraunhofer.iem.dataprovider.toolResult.ToolRunService import jakarta.annotation.PreDestroy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -42,7 +42,7 @@ class DetektDone(override val taskId: UUID, val sarif: Sarif) : TaskDone() class TaskManager( private val config: Config, private val repositoryRepository: RepositoryRepository, - private val toolRunRepository: ToolRunRepository + private val toolRunService: ToolRunService ) { // The used default dispatcher is ok for CPU-bound workloads. However, @@ -128,8 +128,8 @@ class TaskManager( analysisResultsPath, ::addEvent, event.repoId, - repositoryRepository, - toolRunRepository, groupId + groupId, + toolRunService ) val detektTask = DetektTask( @@ -137,9 +137,8 @@ class TaskManager( analysisResultsPath, ::addEvent, event.repoId, - repositoryRepository, - toolRunRepository, - groupID = groupId + groupID = groupId, + toolRunService ) taskIds.add(odcTask.taskID) 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 af6ec9ae60b0d581b0d0c09cfe4b2cd7953cb70d..40355b69c99635155165e5278e969fc238cb632b 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,9 +1,7 @@ package de.fraunhofer.iem.dataprovider.taskManager.tasks - -import de.fraunhofer.iem.dataprovider.gitlab.RepositoryRepository import de.fraunhofer.iem.dataprovider.taskManager.Event -import de.fraunhofer.iem.dataprovider.toolResult.ToolRunRepository +import de.fraunhofer.iem.dataprovider.toolResult.ToolRunService import org.springframework.core.io.ClassPathResource import org.springframework.core.io.Resource import java.nio.file.Path @@ -15,9 +13,8 @@ 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, - override val groupID: UUID? = null + override val groupID: UUID? = null, + override val toolRunService: ToolRunService ) : SarifTask() { private val resource: Resource = ClassPathResource("scripts/detekt.sh") diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/OdcTask.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/OdcTask.kt index 12c653c5607210680322c0eba7f1cc6c0792e22d..bfb92400e26ed0ddfe30c999095a36b30d67bbc2 100644 --- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/OdcTask.kt +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/taskManager/tasks/OdcTask.kt @@ -1,8 +1,7 @@ package de.fraunhofer.iem.dataprovider.taskManager.tasks -import de.fraunhofer.iem.dataprovider.gitlab.RepositoryRepository import de.fraunhofer.iem.dataprovider.taskManager.Event -import de.fraunhofer.iem.dataprovider.toolResult.ToolRunRepository +import de.fraunhofer.iem.dataprovider.toolResult.ToolRunService import org.springframework.core.io.ClassPathResource import org.springframework.core.io.Resource import java.nio.file.Path @@ -15,9 +14,8 @@ class OdcTask( projectPath: String, outputPath: String, override val responseChannel: suspend (task: Event) -> Unit, override val repoId: UUID, - override val repository: RepositoryRepository, - override val toolRunRepository: ToolRunRepository, - override val groupID: UUID? = null + override val groupID: UUID? = null, + override val toolRunService: ToolRunService ) : SarifTask() { private val resource: Resource = ClassPathResource("scripts/odc.sh") 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 f1793de1f065d90848bb39b5362d9a139fa34b75..552f62c0c5430b7a5ab4a4e066b913bec7a9d2b4 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,15 +1,14 @@ 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.taskManager.SarifProcessDone import de.fraunhofer.iem.dataprovider.taskManager.SarifProcessGroupDone -import de.fraunhofer.iem.dataprovider.toolResult.ToolRunRepository +import de.fraunhofer.iem.dataprovider.toolResult.ToolRunService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import java.io.IOException import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -48,8 +47,7 @@ sealed class ProcessTask : Task() { sealed class SarifTask : ProcessTask() { protected abstract val repoId: UUID - protected abstract val repository: RepositoryRepository - protected abstract val toolRunRepository: ToolRunRepository + protected abstract val toolRunService: ToolRunService protected abstract val resultPath: Path @@ -59,16 +57,13 @@ sealed class SarifTask : ProcessTask() { // 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 sarifDb = sarifResult.asDbObject() - val results = toolRunRepository.saveAll(sarifDb) - - repo.ifPresent { - it.addToolResults(results) - repository.save(it) + try { + val sarifResult = getSarifFromFilePath(resultPath) + toolRunService.saveSarif(sarifResult, repoId) + sendResult(sarifResult) + } catch (e: IOException) { + // TODO: send meaningful event and react on error } - sendResult(sarifResult) } override suspend fun sendResult(sarif: Sarif) { diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/RuleRepository.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/RuleRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..ea3d707b160956251748a8407549c4bf3d83dfb9 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/RuleRepository.kt @@ -0,0 +1,10 @@ +package de.fraunhofer.iem.dataprovider.toolResult + +import org.springframework.data.jpa.repository.JpaRepository +import java.util.* + +interface RuleRepository : JpaRepository<Rule, UUID> { + + + fun findByTool_Id(id: UUID): List<Rule> +} \ No newline at end of file diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolRepository.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolRepository.kt new file mode 100644 index 0000000000000000000000000000000000000000..1989e6acfc160d2cd946cb8603391b94a8c14c46 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolRepository.kt @@ -0,0 +1,12 @@ +package de.fraunhofer.iem.dataprovider.toolResult + +import org.springframework.data.jpa.repository.JpaRepository +import java.util.* + +interface ToolRepository : JpaRepository<Tool, UUID> { + fun findByFullNameIgnoreCaseAndNameIgnoreCaseAndVersionIgnoreCase( + fullName: String?, + name: String?, + version: String? + ): Tool? +} \ 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 7b947239769dc9c0ea0d9f26d514828c15322926..76f056948d15c949147c09caa137d9d652fb8e74 100644 --- a/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolRun.kt +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolRun.kt @@ -24,8 +24,7 @@ class ToolRun { @Column(name = "id", nullable = false) var id: UUID? = null - - @OneToOne(orphanRemoval = true, cascade = [CascadeType.ALL]) + @OneToOne(orphanRemoval = true, cascade = [CascadeType.REFRESH, CascadeType.REMOVE, CascadeType.DETACH]) @JoinColumn(name = "tool_id") var tool: Tool? = null @@ -36,6 +35,24 @@ class ToolRun { @ManyToOne(fetch = FetchType.EAGER) var repository: Repository? = null + @OneToMany( + mappedBy = "toolRun", + cascade = [CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REMOVE, CascadeType.DETACH], + orphanRemoval = true + ) + var rules: MutableSet<Rule> = mutableSetOf() + + fun addRules(rules: Collection<Rule>) { + rules.forEach { + this.addRule(it) + } + } + + fun addRule(rule: Rule) { + rules.add(rule) + rule.toolRun = this + } + @OneToMany(mappedBy = "toolRun", cascade = [CascadeType.ALL], orphanRemoval = true) var toolResults: MutableSet<ToolResult> = mutableSetOf() @@ -68,7 +85,11 @@ class Tool { @Column(name = "version") var version: String? = null - @OneToMany(mappedBy = "tool", cascade = [CascadeType.ALL], orphanRemoval = true) + @OneToMany( + mappedBy = "tool", + cascade = [CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REMOVE, CascadeType.DETACH], + orphanRemoval = true + ) var rules: MutableSet<Rule> = mutableSetOf() fun addRules(rules: Collection<Rule>) { @@ -81,6 +102,16 @@ class Tool { rules.add(rule) rule.tool = this } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || Hibernate.getClass(this) != Hibernate.getClass(other)) return false + other as Tool + + return id != null && id == other.id + } + + override fun hashCode(): Int = javaClass.hashCode() } @@ -186,9 +217,16 @@ class Rule { @Column(name = "short_description", columnDefinition = "TEXT") var shortDescription: String? = null - @OneToMany(mappedBy = "rule", cascade = [CascadeType.ALL], orphanRemoval = true) + @OneToMany( + mappedBy = "rule", + cascade = [CascadeType.MERGE, CascadeType.REFRESH, CascadeType.REFRESH, CascadeType.DETACH], + orphanRemoval = true + ) var toolResults: MutableSet<ToolResult> = mutableSetOf() + @ManyToOne + var toolRun: ToolRun? = null + fun addResultLocations(toolResults: Collection<ToolResult>) { toolResults.forEach { this.addResultLocation(it) diff --git a/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolRunService.kt b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolRunService.kt new file mode 100644 index 0000000000000000000000000000000000000000..36f8edc1610758a4df08c316c6d1b2e62d60e5d9 --- /dev/null +++ b/src/main/kotlin/de/fraunhofer/iem/dataprovider/toolResult/ToolRunService.kt @@ -0,0 +1,67 @@ +package de.fraunhofer.iem.dataprovider.toolResult + +import de.fraunhofer.iem.dataprovider.gitlab.RepositoryRepository +import de.fraunhofer.iem.dataprovider.sarif.Sarif +import de.fraunhofer.iem.dataprovider.sarif.asDbObject +import org.springframework.stereotype.Service +import java.util.* + +@Service +class ToolRunService( + private val repositoryRepository: RepositoryRepository, + private val toolRunRepository: ToolRunRepository, + private val toolRepository: ToolRepository, + private val ruleRepository: RuleRepository +) { + + // TODO: this func should be a suspend fun. We can achieve this by + // wrapping the db return types in flux, mono, or futures + fun saveSarif(sarif: Sarif, repoId: UUID) { + + val repo = repositoryRepository.findById(repoId) + + sarif.runs.forEach { run -> + val fullName = run.tool.driver.fullName + val name = run.tool.driver.name + val version = run.tool.driver.version + + val tool = + toolRepository.findByFullNameIgnoreCaseAndNameIgnoreCaseAndVersionIgnoreCase(fullName, name, version) + ?: toolRepository.save(run.tool.asDbObject()) + + val rules = if (tool.id != null) { + ruleRepository.findByTool_Id(tool.id!!).toMutableList() + } else { + mutableListOf() + } + + run.tool.driver.rules.forEach { sarifRule -> + if (rules.find { it.sarifRuleId == sarifRule.id } == null) { + val rule = ruleRepository.save(sarifRule.asDbObject()) + rules.add(rule) + tool.addRule(rule) + } + } + + val toolResults = run.results.map { sarifResult -> + val tr = sarifResult.asDbObject() + rules.filter { it.sarifRuleId == sarifResult.ruleId } + .forEach { it.addResultLocation(tr) } + tr + } + + + val toolRun = run.asDbObject() + toolRun.tool = tool + toolRun.addRules(rules) + toolRun.addToolResults(toolResults) + + val result = toolRunRepository.save(toolRun) + toolRepository.save(tool) + repo.ifPresent { + it.addToolResult(result) + repositoryRepository.save(it) + } + } + } +} \ No newline at end of file