All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.octopusden.octopus.vcsfacade.controller.RepositoryController.kt Maven / Gradle / Ivy

There is a newer version: 3.0.21
Show newest version
package org.octopusden.octopus.vcsfacade.controller

import org.octopusden.octopus.vcsfacade.client.common.Constants
import org.octopusden.octopus.vcsfacade.client.common.dto.Commit
import org.octopusden.octopus.vcsfacade.client.common.dto.PullRequestRequest
import org.octopusden.octopus.vcsfacade.client.common.dto.SearchIssueInRangesResponse
import org.octopusden.octopus.vcsfacade.client.common.dto.SearchIssuesInRangesRequest
import org.octopusden.octopus.vcsfacade.client.common.dto.Tag
import org.octopusden.octopus.vcsfacade.client.common.dto.VcsFacadeResponse
import org.octopusden.octopus.vcsfacade.config.JobProperties
import org.octopusden.octopus.vcsfacade.exception.JobProcessingException
import org.octopusden.octopus.vcsfacade.issue.IssueKeyParser
import org.octopusden.octopus.vcsfacade.service.VCSManager
import org.octopusden.octopus.vcsfacade.service.dto.RepositoryResponse
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.core.task.AsyncTaskExecutor
import org.springframework.format.annotation.DateTimeFormat
import org.springframework.http.MediaType
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.RequestBody
import org.springframework.web.bind.annotation.RequestHeader
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import java.util.Date
import java.util.UUID
import java.util.concurrent.Callable
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ExecutionException
import java.util.concurrent.Future
import java.util.concurrent.TimeUnit
import java.util.stream.Collectors


@RestController
@RequestMapping("repository")
class RepositoryController(
    private val jobProperties: JobProperties,
    private val vcsManager: VCSManager,
    @Qualifier("jobExecutor") private val taskExecutor: AsyncTaskExecutor
) {
    private val requestJobs = ConcurrentHashMap>()

    @GetMapping("commits", produces = [MediaType.APPLICATION_JSON_VALUE])
    fun getCommitsForRelease(
        @RequestParam("vcsPath") vcsPath: String,
        @RequestParam("to") to: String,
        @RequestParam("from", required = false) from: String?,
        @RequestParam("fromDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) fromDate: Date?,
        @RequestHeader(Constants.DEFERRED_RESULT_HEADER, required = false) requestId: String?
    ): List = processJob(requestId ?: UUID.randomUUID().toString()) {
        val commits = vcsManager.getCommits(vcsPath, from, fromDate, to)
        RepositoryResponse(commits)
    }.data

    @Deprecated(
        message="Deprecated endpoint. Does not allow usage of a git ref containing slash as commitId",
        replaceWith = ReplaceWith("getCommit(vcsPath, commitId)")
    )
    @GetMapping("commits/{commitId}", produces = [MediaType.APPLICATION_JSON_VALUE])
    fun getCommitById(@PathVariable("commitId") commitId: String, @RequestParam("vcsPath") vcsPath: String) =
        getCommit(vcsPath, commitId)

    @GetMapping("commit", produces = [MediaType.APPLICATION_JSON_VALUE])
    fun getCommit(@RequestParam("vcsPath") vcsPath: String, @RequestParam("commitId") commitId: String) =
        vcsManager.findCommit(vcsPath, commitId)

    @GetMapping("issues", produces = [MediaType.APPLICATION_JSON_VALUE])
    fun getCommitsForReleaseIssues(
        @RequestParam("vcsPath") vcsPath: String,
        @RequestParam("to") to: String,
        @RequestParam(name = "from", required = false) from: String?,
        @RequestParam("fromDate", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) fromDate: Date?,
        @RequestHeader(Constants.DEFERRED_RESULT_HEADER, required = false) requestId: String?
    ): List = processJob(requestId ?: UUID.randomUUID().toString()) {
        val commits =
            vcsManager.getCommits(vcsPath, from, fromDate, to).flatMap { IssueKeyParser.findIssueKeys(it.message) }
                .distinct()
        RepositoryResponse(commits)
    }.data

    @GetMapping("issues/{issueKey}", produces = [MediaType.APPLICATION_JSON_VALUE])
    fun getCommitsByIssueKey(
        @PathVariable("issueKey") issueKey: String,
        @RequestHeader(Constants.DEFERRED_RESULT_HEADER, required = false) requestId: String?
    ): List =
        processJob(requestId ?: UUID.randomUUID().toString()) {
            val commits = vcsManager.findCommits(issueKey)
            RepositoryResponse(commits)
        }.data


    @GetMapping("tags", produces = [MediaType.APPLICATION_JSON_VALUE])
    fun getTagsForRepository(
        @RequestParam("vcsPath") vcsPath: String,
        @RequestHeader(Constants.DEFERRED_RESULT_HEADER, required = false) requestId: String?
    ): List =
        processJob(requestId ?: UUID.randomUUID().toString()) {
            val commits = vcsManager.getTagsForRepository(vcsPath)
            RepositoryResponse(commits)
        }.data


    @PostMapping(
        "search-issues-in-ranges",
        consumes = [MediaType.APPLICATION_JSON_VALUE],
        produces = [MediaType.APPLICATION_JSON_VALUE]
    )
    fun searchIssuesInRanges(
        @RequestBody searchRequest: SearchIssuesInRangesRequest,
        @RequestHeader(Constants.DEFERRED_RESULT_HEADER, required = false) requestId: String?
    ): SearchIssueInRangesResponse =
        processJob(requestId ?: UUID.randomUUID().toString()) {
            val rangeCommits = searchRequest.ranges
                .flatMap { range ->
                    vcsManager.getCommits(range.vcsPath, range.fromCid, range.fromDate, range.toCid)
                        .map { commit -> commit.id to range }
                }
                .groupBy({ it.first }, { it.second })

            val commitIssueMap = searchRequest.issues
                .flatMap { issue ->
                    vcsManager.findCommits(issue)
                        .map { commit ->
                            val range = rangeCommits[commit.id]
                            issue to range
                        }
                }.groupBy({ (issue, _) -> issue }, { (_, ranges) -> ranges })

                .map { it.key to it.value.filterNotNull().toSet() }
                .stream()
                .collect(Collectors.toMap({ pair -> pair.first },
                    { pair -> pair.second.flatten().toSet() },
                    { a, b -> a + b }
                ))
            return@processJob SearchIssueInRangesResponse(commitIssueMap)
        }

    @PostMapping("pull-requests")
    fun createPullRequest(@RequestParam("vcsPath") vcsPath: String, @RequestBody pullRequestRequest: PullRequestRequest) =
        vcsManager.createPullRequest(vcsPath, pullRequestRequest)

    private fun  processJob(requestId: String, func: () -> T): T {
        log.debug("Process request: $requestId")
        val future = requestJobs.computeIfAbsent(requestId) { processingRequest ->

            log.debug("Add job request: $processingRequest")
            val submittedFuture = taskExecutor.submit(Callable {
                log.debug("Start job request: $processingRequest")
                val result = func.invoke()
                log.trace("Finish job request: $processingRequest")
                result
            })

            val waitThreshold = Date(Date().time + (jobProperties.fastWorkTimoutSecs * 1000))
            while (Date().before(waitThreshold) && !submittedFuture.isDone) {
                TimeUnit.MILLISECONDS.sleep(200)
            }

            submittedFuture
        }

        if (!future.isDone) {
            throw JobProcessingException(
                "Job is processing",
                requestId,
                Date(Date().time + (jobProperties.retryIntervalSecs * 1000))
            )
        }

        log.debug("Return job result: $requestId")
        try {
            return requestJobs.remove(requestId)!!.get() as T
        } catch (e: ExecutionException) {
            throw e.cause!!
        }
    }

    companion object {
        private val log = LoggerFactory.getLogger(RepositoryController::class.java)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy