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.")
}
}
@InheritConstructors
class DrafterException extends Exception { }