Newer
Older
pmd-jenkins-library / src / uk / org / floop / jenkins_pmd / Drafter.groovy
package uk.org.floop.jenkins_pmd

import groovy.json.JsonSlurper
import groovy.transform.InheritConstructors
import hudson.FilePath
import org.apache.http.HttpHost
import org.apache.http.HttpResponse
import org.apache.http.client.fluent.Executor
import org.apache.http.client.fluent.Request
import org.apache.http.entity.ContentType
import org.apache.http.util.EntityUtils

class Drafter implements Serializable {
    private PMD pmd
    private URI apiBase
    private HttpHost host, cacheHost
    private String user, pass, cacheUser, cachePass

    enum Include {
        ALL("all"), OWNED("owned"), CLAIMABLE("claimable")
        public final String value
        Include(String v) {
            this.value = v
        }
    }

    Drafter(PMD pmd, String user, String pass, String cacheUser, String cachePass) {
        this.pmd = pmd
        this.apiBase = new URI(pmd.config.pmd_api)
        this.host = new HttpHost(apiBase.getHost(), apiBase.getPort(), apiBase.getScheme())
        this.user = user
        this.pass = pass
        if (pmd.config.empty_cache) {
            URI cacheBase = new URI(pmd.config.empty_cache)
            this.cacheHost = new HttpHost(cacheBase.getHost(), cacheBase.getPort(), cacheBase.getScheme())
            this.cacheUser = cacheUser
            this.cachePass = cachePass
        }
    }

    private Executor getExec() {
        Executor exec = Executor.newInstance()
                .auth(this.host, this.user, this.pass)
                .authPreemptive(this.host)
        if (pmd.config.cache_credentials) {
            return exec
                    .auth(this.cacheHost, this.cacheUser, this.cachePass)
                    .authPreemptive(this.cacheHost)
        } else {
            return exec
        }
    }

    def listDraftsets(Include include=Include.ALL) {
        def js = new JsonSlurper()
        String path = (include == Include.ALL) ? "/v1/draftsets" : "/v1/draftsets?include=" + include.value
        def response = js.parse(
                getExec().execute(
                        Request.Get(apiBase.resolve(path))
                                .addHeader("Accept", "application/json")
                                .userAgent(PMDConfig.UA)
                ).returnContent().asStream()
        )
        response
    }

    private static String errorMsg(HttpResponse response) {
        EntityUtils.consume(response.getEntity())
        "${response.getStatusLine()} : ${EntityUtils.toString(response.getEntity())}"
    }

    def createDraftset(String label) {
        String displayName = URLEncoder.encode(label, "UTF-8")
        String path = "/v1/draftsets?display-name=${displayName}"
        int retries = 5
        while (retries > 0) {
            HttpResponse response = getExec().execute(
                            Request.Post(apiBase.resolve(path))
                                    .addHeader("Accept", "application/json")
                                    .userAgent(PMDConfig.UA)
                    ).returnResponse()
            if (response.getStatusLine().statusCode == 200) {
                return new JsonSlurper().parse(EntityUtils.toByteArray(response.getEntity()))
            } else if (response.getStatusLine().statusCode == 503) {
                waitForLock()
            } else {
                throw new DrafterException("Problem creating draftset ${errorMsg(response)}")
            }
            retries = retries - 1
        }
        throw new DrafterException("Problem creating draftset, maximum retries reached while waiting for lock.")
    }

    def waitForLock() {
        Boolean waiting = true
        int holdOffTime = 5
        while(waiting) {
            sleep(holdOffTime * 1000)
            HttpResponse response = getExec().execute(
                    Request.Get(apiBase.resolve('/v1/status/writes-locked'))
                            .addHeader('Accept', 'application/json')
                            .userAgent(PMDConfig.UA)
            ).returnResponse()
            if (response.getStatusLine().statusCode == 200) {
                waiting = new JsonSlurper().parse(EntityUtils.toByteArray(response.getEntity()))
            } else {
                throw new DrafterException("Problem waiting for write-lock ${errorMsg(response)}")
            }
            if (waiting && (holdOffTime < 60)) {
                holdOffTime = holdOffTime * 2
            }
        }
    }

    def deleteGraph(String draftsetId, String graph) {
        String encGraph = URLEncoder.encode(graph, "UTF-8")
        String path = "/v1/draftset/${draftsetId}/graph?graph=${encGraph}&silent=true"
        int retries = 5
        while (retries > 0) {
            HttpResponse response = getExec().execute(
                    Request.Delete(apiBase.resolve(path))
                            .addHeader("Accept", "application/json")
                            .userAgent(PMDConfig.UA)
            ).returnResponse()
            if (response.getStatusLine().statusCode == 200) {
                return new JsonSlurper().parse(EntityUtils.toByteArray(response.getEntity()))
            } else if (response.getStatusLine().statusCode == 503) {
                waitForLock()
            } else {
                throw new DrafterException("Problem deleting graph ${errorMsg(response)}")
            }
            retries = retries - 1
        }
        throw new DrafterException("Problem deleting graph, maximum retries reached while waiting for lock.")
    }

    def deleteDraftset(String draftsetId) {
        String path = "/v1/draftset/${draftsetId}"
        int retries = 5
        while (retries > 0) {
            HttpResponse response = getExec().execute(
                            Request.Delete(apiBase.resolve(path))
                                    .addHeader("Accept", "application/json")
                                    .userAgent(PMDConfig.UA)
            ).returnResponse()
            if (response.getStatusLine().statusCode == 202) {
                def jobObj = new JsonSlurper().parse(EntityUtils.toByteArray(response.getEntity()))
                waitForJob(apiBase.resolve(jobObj['finished-job'] as String), jobObj['restart-id'] as String)
                return
            } else if (response.getStatusLine().statusCode == 503) {
                waitForLock()
            } else {
                throw new DrafterException("Problem deleting draftset ${errorMsg(response)}")
            }
            retries = retries - 1
        }
        throw new DrafterException("Problem deleting draftset, maximum retries reached while waiting for lock.")
    }

    def waitForJob(URI finishedJob, String restartId) {
        Executor exec = getExec()
        while (true) {
            HttpResponse jobResponse = exec.execute(
                    Request.Get(finishedJob)
                            .setHeader("Accept", "application/json")
                            .userAgent(PMDConfig.UA)
            ).returnResponse()
            int status = jobResponse.getStatusLine().statusCode
            def jobObj
            try {
                jobObj = new JsonSlurper().parse(EntityUtils.toByteArray(jobResponse.getEntity()))
            } catch(e) {
                throw new DrafterException("Failed waiting for job ${errorMsg(jobResponse)}.\n${e}")
            }
            if (status == 404) {
                if (jobObj['restart-id'] != restartId) {
                    throw new DrafterException("Failed waiting for job to finish, no/different restart-id ${errorMsg(jobResponse)}")
                } else {
                    sleep(10000)
                }
            } else if (status == 200) {
                if (jobObj['restart-id'] != restartId) {
                    throw new DrafterException("Failed waiting for job to finish, restart-id is different.")
                } else if (jobObj['type'] != 'ok') {
                    throw new DrafterException("Pipeline error in ${jobObj.details?.pipeline?.name}. ${jobObj.message}")
                } else {
                    break
                }
            } else {
                throw new DrafterException("Unexpected response waiting for job to complete: ${errorMsg(jobResponse)}")
            }
        }
    }

    def addData(String draftId, String source, String mimeType, String encoding, String graph=null) {
        String path = "/v1/draftset/${draftId}/data"
        if (graph) {
            String encGraph = URLEncoder.encode(graph, "UTF-8")
            path = path + "?graph=${encGraph}"
        }
        int retries = 5
        while (retries > 0) {
            InputStream streamSource
            if (source.startsWith('http')) {
                streamSource = Request
                    .Get(source)
                    .userAgent(PMDConfig.UA)
                    .addHeader('Accept' ,mimeType)
                    .execute().returnContent().asStream()
            } else {
                streamSource = new FilePath(new File(source)).read()
            }
            HttpResponse response = getExec().execute(
                    Request.Put(apiBase.resolve(path))
                            .addHeader("Accept", "application/json")
                            .userAgent(PMDConfig.UA)
                            .bodyStream(streamSource, ContentType.create(mimeType, encoding))
            ).returnResponse()
            if (response.getStatusLine().statusCode == 202) {
                def jobObj = new JsonSlurper().parse(EntityUtils.toByteArray(response.getEntity()))
                waitForJob(apiBase.resolve(jobObj['finished-job'] as String), jobObj['restart-id'] as String)
                return
            } else if (response.getStatusLine().statusCode == 503) {
                waitForLock()
            } else {
                throw new DrafterException("Problem adding data ${errorMsg(response)}")
            }
            retries = retries - 1
        }
        throw new DrafterException("Problem adding data, maximum retries reached while waiting for lock.")
    }

    def findDraftset(String displayName) {
        def drafts = listDraftsets(Include.OWNED)
        def draftset = drafts.find  { it['display-name'] == displayName }
        if (draftset) {
            draftset
        } else {
            throw new DrafterException("Can't find draftset with the display-name '${displayName}'")
        }

    }

    def publishDraftset(String id) {
        String path = "/v1/draftset/${id}/publish"
        Executor exec = getExec()
        int retries = 5
        while (retries > 0) {
            HttpResponse response = exec.execute(
                    Request.Post(apiBase.resolve(path))
                            .addHeader("Accept", "application/json")
                            .userAgent(PMDConfig.UA)
            ).returnResponse()
            if (response.getStatusLine().statusCode == 202) {
                def jobObj = new JsonSlurper().parse(EntityUtils.toByteArray(response.getEntity()))
                waitForJob(apiBase.resolve(jobObj['finished-job'] as String), jobObj['restart-id'] as String)
                if (pmd.config.empty_cache) {
                    exec.execute(
                            Request.Put(pmd.config.empty_cache)
                                    .addHeader("Accept", "application/json")
                                    .userAgent(PMDConfig.UA))
                }
                if (pmd.config.sync_search) {
                    exec.execute(
                            Request.Put(pmd.config.sync_search)
                                    .addHeader("Accept", "application/json")
                                    .userAgent(PMDConfig.UA))
                }
                return
            } else if (response.getStatusLine().statusCode == 503) {
                waitForLock()
            } else {
                throw new DrafterException("Problem publishing draftset ${errorMsg(response)}")
            }
            retries = retries - 1
        }
        throw new DrafterException("Problem publishing draftset, maximum retries reached while waiting for lock.")
    }

    def getDraftsetEndpoint(String id) {
        String path = "/v1/draftset/${id}/query"
        apiBase.resolve(path)
    }

}

@InheritConstructors
class DrafterException extends Exception { }