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

org.octopusden.octopus.infrastructure.bitbucket.client.BitbucketClient.kt Maven / Gradle / Ivy

There is a newer version: 2.0.55
Show newest version
package org.octopusden.octopus.infrastructure.bitbucket.client

import feign.Headers
import feign.Param
import feign.QueryMap
import feign.RequestLine
import java.util.Date
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BaseBitbucketEntity
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketBranch
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCommit
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCommitChange
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCreatePrRef
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCreateProject
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCreatePullRequest
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCreatePullRequestReviewer
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCreateRepository
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketCreateTag
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketEntityList
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketJiraCommit
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketProject
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketPullRequest
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketRepository
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketTag
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketUpdateRepository
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.BitbucketUser
import org.octopusden.octopus.infrastructure.bitbucket.client.dto.DefaultReviewersQuery
import org.octopusden.octopus.infrastructure.bitbucket.client.exception.InvalidCommitIdException
import org.octopusden.octopus.infrastructure.bitbucket.client.exception.NotFoundException
import org.slf4j.Logger
import org.slf4j.LoggerFactory

private val _log: Logger = LoggerFactory.getLogger(BitbucketClient::class.java)

const val PROJECT_PATH = "rest/api/1.0/projects"
const val REPO_PATH = "rest/api/1.0/repos"
const val GIT_PROJECT_PATH = "/rest/git/1.0/projects"
const val JIRA_ISSUES_PATH = "rest/jira/1.0/issues"
const val DEFAULT_REVIEWERS_PATH = "rest/default-reviewers/1.0/projects"
const val ENTITY_LIMIT = 100

interface BitbucketClient {

    @RequestLine("GET $PROJECT_PATH")
    fun getProjects(@QueryMap requestParams: Map): BitbucketEntityList

    @RequestLine("GET $REPO_PATH")
    fun getRepositories(@QueryMap requestParams: Map): BitbucketEntityList

    @RequestLine("POST $PROJECT_PATH")
    @Headers("Content-Type: application/json")
    fun createProject(dto: BitbucketCreateProject)

    @RequestLine("GET $PROJECT_PATH/{projectKey}")
    @Throws(NotFoundException::class)
    fun getProject(@Param("projectKey") projectKey: String): BitbucketProject

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos")
    fun getRepositories(
        @Param("projectKey") projectKey: String,
        @QueryMap requestParams: Map
    ): BitbucketEntityList

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos/{repository}")
    @Throws(NotFoundException::class)
    fun getRepository(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String
    ): BitbucketRepository

    @RequestLine("POST $PROJECT_PATH/{projectKey}/repos")
    @Headers("Content-Type: application/json")
    fun createRepository(@Param("projectKey") projectKey: String, dto: BitbucketCreateRepository)

    @RequestLine("PUT $PROJECT_PATH/{projectKey}/repos/{repository}")
    @Headers("Content-Type: application/json")
    fun updateRepository(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        dto: BitbucketUpdateRepository
    )

    @RequestLine("DELETE $PROJECT_PATH/{projectKey}/repos/{repository}")
    fun deleteRepository(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
    )

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos/{repository}/commits")
    fun getCommits(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @QueryMap requestParams: Map
    ): BitbucketEntityList

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos/{repository}/commits/{id}")
    @Throws(NotFoundException::class)
    @Suppress("FunctionName")
    fun _getCommit(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @Param("id", expander = BitbucketCommitIdValidator::class) id: String
    ): BitbucketCommit

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos/{repository}/commits/{id}/changes")
    @Throws(NotFoundException::class)
    @Suppress("FunctionName")
    fun _getCommitChanges(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @Param("id", expander = BitbucketCommitIdValidator::class) id: String
    ): BitbucketEntityList

    @RequestLine("GET $JIRA_ISSUES_PATH/{issueKey}/commits")
    fun getCommits(
        @Param("issueKey") issueKey: String,
        @QueryMap requestParams: Map
    ): BitbucketEntityList

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos/{repository}/tags")
    fun getTags(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @QueryMap requestParams: Map,
    ): BitbucketEntityList

    @RequestLine("POST $PROJECT_PATH/{projectKey}/repos/{repository}/tags")
    @Headers("Content-Type: application/json")
    fun createTag(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        dto: BitbucketCreateTag
    ): BitbucketTag

    @RequestLine("DELETE $GIT_PROJECT_PATH/{projectKey}/repos/{repository}/tags/{tag}", decodeSlash = false)
    fun deleteTag(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @Param("tag") tag: String
    )

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos/{repository}/branches")
    fun getBranches(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @QueryMap requestParams: Map,
    ): BitbucketEntityList

    @RequestLine("GET $DEFAULT_REVIEWERS_PATH/{projectKey}/repos/{repository}/reviewers")
    fun getDefaultReviewers(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @QueryMap query: DefaultReviewersQuery
    ): Set

    @RequestLine("POST $PROJECT_PATH/{projectKey}/repos/{repository}/pull-requests")
    @Headers("Content-Type: application/json")
    fun createPullRequest(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        dto: BitbucketCreatePullRequest
    ): BitbucketPullRequest

    @RequestLine("GET $PROJECT_PATH/{projectKey}/repos/{repository}/pull-requests/{id}")
    @Headers("Content-Type: application/json")
    fun getPullRequest(
        @Param("projectKey") projectKey: String,
        @Param("repository") repository: String,
        @Param("id") id: Long
    ): BitbucketPullRequest
}

fun BitbucketClient.getProjects() = execute(
    { parameters: Map -> getProjects(parameters) }
)

fun BitbucketClient.getRepositories() = execute(
    { parameters: Map -> getRepositories(parameters) }
)

fun BitbucketClient.getRepositories(projectKey: String) = execute(
    { parameters: Map -> getRepositories(projectKey, parameters) }
)

private fun String.isBranchId() = startsWith("refs/heads/")

private fun String.isTagId() = startsWith("refs/tags/")

private fun BitbucketClient.findBranch(projectKey: String, repository: String, branch: String): BitbucketBranch? {
    val isId = branch.isBranchId()
    return execute({ parameters: Map ->
        getBranches(
            projectKey,
            repository,
            parameters + mapOf("filterText" to if (isId) branch.substring(10) else branch)
        )
    }).find { if (isId) it.id == branch else it.displayId == branch }
}

private fun BitbucketClient.findTag(projectKey: String, repository: String, tag: String): BitbucketTag? {
    val isId = tag.isTagId()
    return execute({ parameters: Map ->
        getTags(
            projectKey,
            repository,
            parameters + mapOf("filterText" to if (isId) tag.substring(10) else tag)
        )
    }).find { if (isId) it.id == tag else it.displayId == tag }
}

private fun BitbucketClient.findRef(projectKey: String, repository: String, ref: String) =
    if (ref.isBranchId()) findBranch(projectKey, repository, ref)
    else if (ref.isTagId()) findTag(projectKey, repository, ref)
    else findBranch(projectKey, repository, ref) ?: findTag(projectKey, repository, ref)

fun BitbucketClient.getCommit(projectKey: String, repository: String, commitIdOrRef: String) =
    try {
        _getCommit(projectKey, repository, commitIdOrRef)
    } catch (e: InvalidCommitIdException) {
        _log.info("Treat `$commitIdOrRef` as a ref. ${e.message}")
        findRef(projectKey, repository, commitIdOrRef)?.let {
            _getCommit(projectKey, repository, it.latestCommit)
        } ?: throw NotFoundException("Ref '$commitIdOrRef' does not exist in repository '$repository' and ${e.message}")
    }

fun BitbucketClient.getCommitChanges(
    projectKey: String,
    repository: String,
    commitIdOrRef: String
) = try {
    _getCommitChanges(projectKey, repository, commitIdOrRef)
} catch (e: InvalidCommitIdException) {
    _log.info("Treat `$commitIdOrRef` as a ref. ${e.message}")
    findRef(projectKey, repository, commitIdOrRef)?.let {
        _getCommitChanges(projectKey, repository, it.latestCommit)
    } ?: throw NotFoundException("Ref '$commitIdOrRef' does not exist in repository '$repository' and ${e.message}")
}.values

fun BitbucketClient.getCommits(
    projectKey: String, repository: String, until: String, since: String
): List {
    val toId = getCommit(projectKey, repository, until).id
    val fromId = getCommit(projectKey, repository, since).id
    return if (toId == fromId) {
        emptyList()
    } else {
        execute({ parameters: Map ->
            getCommits(projectKey, repository, parameters + mapOf("until" to toId, "since" to fromId))
        }).also { commits ->
            if (!commits.any { commit -> commit.parents.any { it.id == fromId } }) {
                throw NotFoundException("Cannot find commit '$fromId' in commit graph for commit '$toId' in '$projectKey:$repository'")
            }
        }
    }
}

fun BitbucketClient.getCommits(
    projectKey: String, repository: String, until: String, sinceDate: Date? = null
) = execute({ parameters: Map ->
    getCommits(projectKey, repository, parameters + mapOf("until" to until))
}, { commit: BitbucketCommit ->
    sinceDate == null || commit.authorTimestamp > sinceDate
})

fun BitbucketClient.getCommits(issueKey: String) = execute(
    { parameters: Map -> getCommits(issueKey, parameters) }
)

fun BitbucketClient.getTags(projectKey: String, repository: String) = execute(
    { parameters: Map -> getTags(projectKey, repository, parameters) }
)

fun BitbucketClient.getTag(projectKey: String, repository: String, tag: String) =
    findTag(projectKey, repository, tag)
        ?: throw NotFoundException("Tag '$tag' is not found in '$projectKey:$repository'")

fun BitbucketClient.getBranches(projectKey: String, repository: String) = execute(
    { parameters: Map -> getBranches(projectKey, repository, parameters) }
)

fun BitbucketClient.getBranch(projectKey: String, repository: String, branch: String) =
    findBranch(projectKey, repository, branch)
        ?: throw NotFoundException("Branch '$branch' is not found in '$projectKey:$repository'")

fun BitbucketClient.createPullRequestWithDefaultReviewers(
    projectKey: String,
    repository: String,
    sourceBranch: String,
    targetBranch: String,
    title: String,
    description: String
) = getRepository(projectKey, repository).let {
    val source = BitbucketCreatePrRef(getBranch(projectKey, repository, sourceBranch).id, it)
    val target = BitbucketCreatePrRef(getBranch(projectKey, repository, targetBranch).id, it)
    val defaultReviewers = getDefaultReviewers(
        projectKey, repository, DefaultReviewersQuery(it.id, source.id, it.id, target.id)
    ).map { users -> BitbucketCreatePullRequestReviewer(users) }
    createPullRequest(
        projectKey, repository, BitbucketCreatePullRequest(title, description, source, target, defaultReviewers)
    )
}

private fun > execute(
    function: (Map) -> BitbucketEntityList,
    filter: (element: T) -> Boolean = { true }
): List {
    var page = 0
    var pageStart = 0
    val entities = mutableListOf()
    val parameters = mutableMapOf("limit" to ENTITY_LIMIT)
    do {
        page++
        parameters["start"] = pageStart
        val currentPartEntities = function.invoke(parameters)
        val inFilter: Boolean = with(currentPartEntities.values.all(filter)) {
            entities += if (this) {
                currentPartEntities.values
            } else {
                currentPartEntities.values.filter(filter)
            }
            this
        }
        pageStart = currentPartEntities.nextPageStart ?: pageStart
    } while (!currentPartEntities.isLastPage && inFilter)
    _log.debug("Pages retrieved: $page")
    return entities
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy