package org.jfrog.build.extractor.clientConfiguration.client; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.entity.StringEntity; import org.jfrog.build.api.util.Log; import org.jfrog.build.client.PreemptiveHttpClient; import org.jfrog.build.client.artifactoryXrayResponse.ArtifactoryXrayResponse; import java.io.IOException; /** * Created by romang on 1/1/17. */ public class ArtifactoryXrayClient extends ArtifactoryBaseClient { private static final String SCAN_BUILD_URL = "/api/xray/scanBuild"; private static final int XRAY_SCAN_RETRY_CONSECUTIVE_RETRIES = 10; // Retrying to resume the scan 10 times after a stable connection private static final int XRAY_SCAN_CONNECTION_TIMEOUT_SECS = 90; // Expecting \r\n every 30 seconds private static final int XRAY_SCAN_SLEEP_BETWEEN_RETRIES_MILLIS = 15000; // 15 seconds sleep between retry private static final String XRAY_FATAL_FAIL_STATUS = "-1"; //Fatal error code from Xray public ArtifactoryXrayClient(String artifactoryUrl, String username, String password, Log logger) { super(artifactoryUrl, username, password, logger); } public ArtifactoryXrayResponse xrayScanBuild(String buildName, String buildNumber, String context) throws IOException, InterruptedException { StringEntity entity = new StringEntity("{\"buildName\":\"" + buildName + "\",\"buildNumber\":\"" + buildNumber + "\",\"context\":\"" + context + "\"}"); entity.setContentType("application/json"); String scanUrl = artifactoryUrl + SCAN_BUILD_URL; HttpPost httpPost = new HttpPost(scanUrl); httpPost.setEntity(entity); return execute(httpPost); } /** * Stable connection is a connection which was connected successfully for at least @stableConnectionMillis. * * @param lastConnectionAttemptMillis * @return */ private boolean isStableConnection(long lastConnectionAttemptMillis) { final long stableConnectionMillis = (XRAY_SCAN_CONNECTION_TIMEOUT_SECS + 10) * 1000; return lastConnectionAttemptMillis + stableConnectionMillis < System.currentTimeMillis(); } private ArtifactoryXrayResponse parseXrayScanResponse(HttpResponse response) throws IOException { if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { response.getEntity().getContent().close(); throw new IOException("Artifactory response: " + response.getStatusLine().getReasonPhrase()); } ObjectMapper mapper = new ObjectMapper(); JsonNode result = mapper.readTree(response.getEntity().getContent()); if (result.get("errors") != null) { String resultStr = result.get("errors").toString(); for (JsonNode error : result.get("errors")) { if (error.get("status").toString().equals(XRAY_FATAL_FAIL_STATUS)) { throw new RuntimeException("Artifactory response: " + resultStr); } } throw new IOException("Artifactory response: " + resultStr); } mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); return mapper.treeToValue(result, ArtifactoryXrayResponse.class); } private ArtifactoryXrayResponse execute(HttpRequestBase httpRequest) throws InterruptedException, IOException { PreemptiveHttpClient client = httpClient.getHttpClient(XRAY_SCAN_CONNECTION_TIMEOUT_SECS); int retryNum = 0; long lastConnectionAttemptMillis = 0; while (true) { try { lastConnectionAttemptMillis = System.currentTimeMillis(); retryNum++; HttpResponse response = client.execute(httpRequest); return parseXrayScanResponse(response); } catch (IOException e) { if (isStableConnection(lastConnectionAttemptMillis)) { retryNum = 0; continue; } if (XRAY_SCAN_RETRY_CONSECUTIVE_RETRIES <= retryNum) { throw e; } log.warn("Xray scan connection lost: " + e.getMessage() + ", attempting to reconnect..."); // Sleeping before trying to reconnect. Thread.sleep(XRAY_SCAN_SLEEP_BETWEEN_RETRIES_MILLIS); } finally { httpRequest.releaseConnection(); } } } }