/* * Copyright (C) 2011 JFrog Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jfrog.build.extractor.clientConfiguration.client; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.base.Function; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.StatusLine; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.FileEntity; import org.apache.http.entity.StringEntity; import org.apache.http.protocol.HTTP; import org.apache.http.util.EntityUtils; import org.jfrog.build.api.Build; import org.jfrog.build.api.BuildRetention; import org.jfrog.build.api.release.BintrayUploadInfoOverride; import org.jfrog.build.api.release.Distribution; import org.jfrog.build.api.release.Promotion; import org.jfrog.build.api.util.FileChecksumCalculator; import org.jfrog.build.api.util.Log; import org.jfrog.build.client.*; import org.jfrog.build.client.bintrayResponse.BintrayResponse; import org.jfrog.build.client.bintrayResponse.BintrayResponseFactory; import org.jfrog.build.extractor.clientConfiguration.util.DeploymentUrlUtils; import org.jfrog.build.util.VersionCompatibilityType; import org.jfrog.build.util.VersionException; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.util.*; import static org.jfrog.build.client.ArtifactoryHttpClient.encodeUrl; /** * Artifactory client to perform build info related tasks. * * @author Yossi Shaul */ public class ArtifactoryBuildInfoClient extends ArtifactoryBaseClient { private static final String LOCAL_REPOS_REST_URL = "/api/repositories?type=local"; private static final String REMOTE_REPOS_REST_URL = "/api/repositories?type=remote"; private static final String VIRTUAL_REPOS_REST_URL = "/api/repositories?type=virtual"; private static final String PUSH_TO_BINTRAY_REST_URL = "/api/build/pushToBintray/"; private static final String BUILD_REST_URL = "/api/build"; private static final String BUILD_RETENTION_REST_URL = BUILD_REST_URL + "/retention/"; private static final int CHECKSUM_DEPLOY_MIN_FILE_SIZE = 10240; // Try checksum deploy of files greater than 10KB public static final String BUILD_BROWSE_URL = "/webapp/builds"; public static final String APPLICATION_VND_ORG_JFROG_ARTIFACTORY_JSON = "application/vnd.org.jfrog.artifactory+json"; public static final String APPLICATION_JSON = "application/json"; /** * Version of Artifactory we work with. */ private ArtifactoryVersion artifactoryVersion; /** * Creates a new client for the given Artifactory url. * * @param artifactoryUrl Artifactory url in the form of: protocol://host[:port]/contextPath */ public ArtifactoryBuildInfoClient(String artifactoryUrl, Log log) { this(artifactoryUrl, null, null, log); } /** * Creates a new client for the given Artifactory url. * * @param artifactoryUrl Artifactory url in the form of: protocol://host[:port]/contextPath * @param username Authentication username * @param password Authentication password */ public ArtifactoryBuildInfoClient(String artifactoryUrl, String username, String password, Log log) { super(artifactoryUrl, username, password, log); } /** * @return A list of local repositories available for deployment. * @throws IOException On any connection error */ public List<String> getLocalRepositoriesKeys() throws IOException { return getRepositoriesList(LOCAL_REPOS_REST_URL); } /** * @return A list of local and cache repositories. * @throws IOException On any connection error */ public List<String> getLocalAndCacheRepositoriesKeys() throws IOException { List<String> localRepositoriesKeys = getLocalRepositoriesKeys(); List<String> remoteRepositories = getRemoteRepositoriesKeys(); List<String> cacheRepositories = Lists.transform(remoteRepositories, new Function<String, String>() { @Override public String apply(String repoKey) { return repoKey + "-cache"; } }); return Lists.newArrayList(Iterables.concat(localRepositoriesKeys, cacheRepositories)); } /** * @return A list of remote repositories. * @throws IOException On any connection error */ public List<String> getRemoteRepositoriesKeys() throws IOException { return getRepositoriesList(REMOTE_REPOS_REST_URL); } /** * @return A list of virtual repositories available for resolution. * @throws IOException On any connection error */ public List<String> getVirtualRepositoryKeys() throws IOException { return getRepositoriesList(VIRTUAL_REPOS_REST_URL); } private List<String> getRepositoriesList(String restUrl) throws IOException { List<String> repositories = new ArrayList<String>(); PreemptiveHttpClient client = httpClient.getHttpClient(); String reposUrl = artifactoryUrl + restUrl; log.debug("Requesting repositories list from: " + reposUrl); HttpGet httpget = new HttpGet(reposUrl); HttpResponse response = client.execute(httpget); StatusLine statusLine = response.getStatusLine(); HttpEntity entity = response.getEntity(); if (statusLine.getStatusCode() != HttpStatus.SC_OK) { throw new IOException("Failed to obtain list of repositories. Status code: " + statusLine.getStatusCode() + getMessageFromEntity(entity)); } else { if (entity != null) { repositories = new ArrayList<String>(); InputStream content = entity.getContent(); JsonParser parser; try { parser = httpClient.createJsonParser(content); JsonNode result = parser.readValueAsTree(); log.debug("Repositories result = " + result); for (JsonNode jsonNode : result) { String repositoryKey = jsonNode.get("key").asText(); repositories.add(repositoryKey); } } finally { if (content != null) { content.close(); } } } } return repositories; } public void sendBuildInfo(String buildInfoJson) throws IOException { String url = artifactoryUrl + BUILD_REST_URL; HttpPut httpPut = new HttpPut(url); try { sendDescriptor(httpPut, buildInfoJson, APPLICATION_VND_ORG_JFROG_ARTIFACTORY_JSON); } catch (IOException e) { throw new IOException("Failed to send build descriptor. " + e.getMessage(), e); } } public void sendBuildRetetion(BuildRetention buildRetantion, String buildName) throws IOException { String buildRetantionJson = toJsonString(buildRetantion); String url = artifactoryUrl + BUILD_RETENTION_REST_URL + buildName; HttpPost httpPost = new HttpPost(url); try { log.info("Executing build retention"); log.debug(buildRetantionJson); sendRequest(httpPost, buildRetantionJson, APPLICATION_JSON, true); } catch (IOException e) { log.error("Failed to execute build retention.", e); throw new IOException("Failed to execute build retention: " + e.getMessage(), e); } } /** * Sends build info to Artifactory. * * @param buildInfo The build info to send * @throws IOException On any connection error */ public void sendBuildInfo(Build buildInfo) throws IOException { log.debug("Sending build info: " + buildInfo); try { sendBuildInfo(buildInfoToJsonString(buildInfo)); } catch (Exception e) { log.error("Could not build the build-info object.", e); throw new IOException("Could not publish build-info: " + e.getMessage()); } String url = artifactoryUrl + BUILD_BROWSE_URL + "/" + encodeUrl(buildInfo.getName()) + "/" + encodeUrl(buildInfo.getNumber()); log.info("Build successfully deployed. Browse it in Artifactory under " + url); } public void sendModuleInfo(Build build) throws IOException { log.debug("Sending build info modules: " + build); try { String url = artifactoryUrl + BUILD_REST_URL + "/append/" + encodeUrl(build.getName()) + "/" + encodeUrl(build.getNumber()); HttpPost httpPost = new HttpPost(url); String modulesAsJsonString = toJsonString(build.getModules()); sendDescriptor(httpPost, modulesAsJsonString, APPLICATION_VND_ORG_JFROG_ARTIFACTORY_JSON); } catch (Exception e) { log.error("Could not build the build-info modules object.", e); throw new IOException("Could not publish build-info modules: " + e.getMessage(), e); } } private void sendDescriptor(HttpEntityEnclosingRequestBase request, String content, String contentType) throws IOException { log.info("Deploying build descriptor to: " + request.getURI().toString()); sendRequest(request, content, contentType, true); } private void sendRequest(HttpEntityEnclosingRequestBase request, String content, String contentType, boolean retry) throws IOException { StringEntity stringEntity = new StringEntity(content, "UTF-8"); stringEntity.setContentType(contentType); request.setEntity(stringEntity); int connectionRetries = httpClient.getConnectionRetries(); try { if (!retry) { httpClient.setConnectionRetries(0); } HttpResponse response = httpClient.getHttpClient().execute(request); StatusLine statusLine = response.getStatusLine(); if (statusLine.getStatusCode() != HttpStatus.SC_NO_CONTENT) { HttpEntity responseEntity = response.getEntity(); throw new IOException(statusLine.getStatusCode() + getMessageFromEntity(responseEntity)); } } finally { // We are using the same client for multiple operations therefore we need to restore the connectionRetries configuration. httpClient.setConnectionRetries(connectionRetries); } } public String getItemLastModified(String path) throws IOException, ParseException { String url = artifactoryUrl + "/api/storage/" + path + "?lastModified&deep=1"; HttpGet get = new HttpGet(url); HttpResponse response = httpClient.getHttpClient().execute(get); StatusLine statusLine = response.getStatusLine(); if (statusLine.getStatusCode() != HttpStatus.SC_OK) { HttpEntity entity = response.getEntity(); throw new IOException("Failed to obtain item info. Status code: " + statusLine.getStatusCode() + getMessageFromEntity(entity)); } else { HttpEntity entity = response.getEntity(); if (entity != null) { InputStream content = entity.getContent(); JsonParser parser; try { parser = httpClient.createJsonParser(content); JsonNode result = parser.readValueAsTree(); return result.get("lastModified").asText(); } finally { if (content != null) { content.close(); } EntityUtils.consume(entity); } } } return null; } /** * Deploys the artifact to the destination repository. * * @param details Details about the deployed artifact * @return ArtifactoryResponse The response content received from Artifactory * @throws IOException On any connection error */ public ArtifactoryUploadResponse deployArtifact(DeployDetails details) throws IOException { StringBuilder deploymentPathBuilder = new StringBuilder(artifactoryUrl); deploymentPathBuilder.append("/").append(details.getTargetRepository()); if (!details.getArtifactPath().startsWith("/")) { deploymentPathBuilder.append("/"); } deploymentPathBuilder.append(details.getArtifactPath()); String deploymentPath = deploymentPathBuilder.toString(); log.info("Deploying artifact: " + deploymentPath); deploymentPath = encodeUrl(deploymentPath); ArtifactoryUploadResponse response = uploadFile(details, deploymentPath); // Artifactory 2.3.2+ will take the checksum from the headers of the put request for the file if (!getArtifactoryVersion().isAtLeast(new ArtifactoryVersion("2.3.2"))) { uploadChecksums(details, deploymentPath); } return response; } /** * @return Artifactory version if working against a compatible version of Artifactory * @throws IOException If server not found or it doesn't answer to the version query or it is too old */ public ArtifactoryVersion verifyCompatibleArtifactoryVersion() throws VersionException { ArtifactoryVersion version; try { version = httpClient.getVersion(); } catch (IOException e) { throw new VersionException("Error occurred while requesting version information: " + e.getMessage(), e, VersionCompatibilityType.NOT_FOUND); } if (version.isNotFound()) { throw new VersionException( "There is either an incompatible or no instance of Artifactory at the provided URL.", VersionCompatibilityType.NOT_FOUND); } boolean isCompatibleArtifactory = version.isAtLeast(ArtifactoryHttpClient.MINIMAL_ARTIFACTORY_VERSION); if (!isCompatibleArtifactory) { throw new VersionException("This plugin is compatible with version " + ArtifactoryHttpClient.MINIMAL_ARTIFACTORY_VERSION + " of Artifactory and above. Please upgrade your Artifactory server!", VersionCompatibilityType.INCOMPATIBLE); } return version; } /** * Push build to bintray * * @param buildName name of the build to push * @param buildNumber number of the build to push * @param signMethod flags if this artifacts should be signed or not * @param passphrase passphrase in case that the artifacts should be signed * @param bintrayUploadInfo request body which contains the upload info * @return http Response with the response outcome * @throws IOException On any connection error * @see org.jfrog.build.api.release.BintrayUploadInfoOverride; */ public BintrayResponse pushToBintray(String buildName, String buildNumber, String signMethod, String passphrase, BintrayUploadInfoOverride bintrayUploadInfo) throws IOException, URISyntaxException { if (StringUtils.isBlank(buildName)) { throw new IllegalArgumentException("Build name is required for promotion."); } if (StringUtils.isBlank(buildNumber)) { throw new IllegalArgumentException("Build number is required for promotion."); } String requestUrl = createRequestUrl(buildName, buildNumber, signMethod, passphrase); String requestBody = createJsonRequestBody(bintrayUploadInfo); HttpPost httpPost = new HttpPost(requestUrl); StringEntity entity = new StringEntity(requestBody); entity.setContentType("application/json"); httpPost.setEntity(entity); HttpResponse response = httpClient.getHttpClient().execute(httpPost); return parseResponse(response); } // create pushToBintray request Url private String createRequestUrl(String buildName, String buildNumber, String signMethod, String passphrase) throws URISyntaxException { URIBuilder urlBuilder = new URIBuilder(); urlBuilder.setPath(artifactoryUrl + PUSH_TO_BINTRAY_REST_URL + buildName + "/" + buildNumber); if (StringUtils.isNotEmpty(passphrase)) { urlBuilder.setParameter("gpgPassphrase", passphrase); } if (!StringUtils.equals(signMethod, "descriptor")) { urlBuilder.setParameter("gpgSign", signMethod); } return urlBuilder.toString(); } // create pushToBintray request body private String createJsonRequestBody(BintrayUploadInfoOverride info) throws IOException { String bintrayInfoJson; if (!info.isValid()) { // empty json body to use the descriptor file if exists bintrayInfoJson = "{}"; } else { bintrayInfoJson = toJsonString(info); } return bintrayInfoJson; } private BintrayResponse parseResponse(HttpResponse response) throws IOException { InputStream content = response.getEntity().getContent(); int status = response.getStatusLine().getStatusCode(); JsonParser parser = httpClient.createJsonFactory().createJsonParser(content); BintrayResponse responseObject = BintrayResponseFactory.createResponse(status, parser); return responseObject; } public HttpResponse stageBuild(String buildName, String buildNumber, Promotion promotion) throws IOException { if (StringUtils.isBlank(buildName)) { throw new IllegalArgumentException("Build name is required for promotion."); } if (StringUtils.isBlank(buildNumber)) { throw new IllegalArgumentException("Build number is required for promotion."); } StringBuilder urlBuilder = new StringBuilder(artifactoryUrl).append(BUILD_REST_URL).append("/promote/"). append(encodeUrl(buildName)).append("/").append(encodeUrl(buildNumber)); String promotionJson = toJsonString(promotion); HttpPost httpPost = new HttpPost(urlBuilder.toString()); StringEntity stringEntity = new StringEntity(promotionJson); stringEntity.setContentType("application/vnd.org.jfrog.artifactory.build.PromotionRequest+json"); httpPost.setEntity(stringEntity); log.info("Promoting build " + buildName + ", #" + buildNumber); return httpClient.getHttpClient().execute(httpPost); } public HttpResponse distributeBuild(String buildName, String buildNumber, Distribution promotion) throws IOException { if (StringUtils.isBlank(buildName)) { throw new IllegalArgumentException("Build name is required for distribution."); } if (StringUtils.isBlank(buildNumber)) { throw new IllegalArgumentException("Build number is required for distribution."); } StringBuilder urlBuilder = new StringBuilder(artifactoryUrl).append(BUILD_REST_URL).append("/distribute/"). append(encodeUrl(buildName)).append("/").append(encodeUrl(buildNumber)); String distributionJson = toJsonString(promotion); HttpPost httpPost = new HttpPost(urlBuilder.toString()); StringEntity stringEntity = new StringEntity(distributionJson); stringEntity.setContentType("application/json"); httpPost.setEntity(stringEntity); log.info("Distributing build " + buildName + ", #" + buildNumber); return httpClient.getHttpClient().execute(httpPost); } public Map<String, List<Map>> getUserPluginInfo() throws IOException { String url = new StringBuilder(artifactoryUrl).append("/api/plugins").toString(); HttpGet getPlugins = new HttpGet(url); HttpResponse getResponse = httpClient.getHttpClient().execute(getPlugins); StatusLine statusLine = getResponse.getStatusLine(); HttpEntity responseEntity = getResponse.getEntity(); if (statusLine.getStatusCode() != HttpStatus.SC_OK) { throw new IOException("Failed to obtain user plugin information. Status code: " + statusLine.getStatusCode() + getMessageFromEntity(responseEntity)); } else { if (responseEntity != null) { InputStream content = responseEntity.getContent(); JsonParser parser; try { parser = httpClient.createJsonParser(content); return parser.readValueAs(Map.class); } finally { if (content != null) { content.close(); } } } } return Maps.newHashMap(); } public HttpResponse executeUserPlugin(String executionName, Map<String, String> requestParams) throws IOException { StringBuilder urlBuilder = new StringBuilder(artifactoryUrl).append("/api/plugins/execute/") .append(executionName).append("?"); appendParamsToUrl(requestParams, urlBuilder); HttpPost postRequest = new HttpPost(urlBuilder.toString()); return httpClient.getHttpClient().execute(postRequest); } public Map getStagingStrategy(String strategyName, String buildName, Map<String, String> requestParams) throws IOException { StringBuilder urlBuilder = new StringBuilder(artifactoryUrl).append("/api/plugins/build/staging/") .append(encodeUrl(strategyName)).append("?buildName=") .append(encodeUrl(buildName)).append("&"); appendParamsToUrl(requestParams, urlBuilder); HttpGet getRequest = new HttpGet(urlBuilder.toString()); HttpResponse response = httpClient.getHttpClient().execute(getRequest); StatusLine statusLine = response.getStatusLine(); HttpEntity responseEntity = response.getEntity(); if (statusLine.getStatusCode() != HttpStatus.SC_OK) { throw new IOException("Failed to obtain staging strategy. Status code: " + statusLine.getStatusCode() + getMessageFromEntity(responseEntity)); } else { if (responseEntity != null) { InputStream content = responseEntity.getContent(); JsonParser parser; try { parser = httpClient.createJsonParser(content); return parser.readValueAs(Map.class); } finally { if (content != null) { content.close(); } } } } return Maps.newHashMap(); } public HttpResponse executePromotionUserPlugin(String promotionName, String buildName, String buildNumber, Map<String, String> requestParams) throws IOException { StringBuilder urlBuilder = new StringBuilder(artifactoryUrl).append("/api/plugins/build/promote/") .append(promotionName).append("/").append(encodeUrl(buildName)).append("/") .append(encodeUrl(buildNumber)).append("?"); appendParamsToUrl(requestParams, urlBuilder); HttpPost postRequest = new HttpPost(urlBuilder.toString()); return httpClient.getHttpClient().execute(postRequest); } public HttpResponse executeUpdateFileProperty(String itemPath, String properties) throws IOException { StringBuilder urlBuilder = new StringBuilder(artifactoryUrl).append("/api/storage/") .append(encodeUrl(itemPath)).append("?").append("properties=").append(encodeUrl(properties)); HttpPut postRequest = new HttpPut(urlBuilder.toString()); return httpClient.getHttpClient().execute(postRequest); } private void appendParamsToUrl(Map<String, String> requestParams, StringBuilder urlBuilder) throws UnsupportedEncodingException { if ((requestParams != null) && !requestParams.isEmpty()) { urlBuilder.append("params="); Iterator<Map.Entry<String, String>> paramEntryIterator = requestParams.entrySet().iterator(); String encodedPipe = encodeUrl("|"); while (paramEntryIterator.hasNext()) { Map.Entry<String, String> paramEntry = paramEntryIterator.next(); urlBuilder.append(encodeUrl(paramEntry.getKey())); String paramValue = paramEntry.getValue(); if (StringUtils.isNotBlank(paramValue)) { urlBuilder.append("=").append(encodeUrl(paramValue)); } if (paramEntryIterator.hasNext()) { urlBuilder.append(encodedPipe); } } } } public String buildInfoToJsonString(Build buildInfo) throws Exception { ArtifactoryVersion version = verifyCompatibleArtifactoryVersion(); //From Artifactory 2.2.3 we do not need to discard new properties in order to avoid a server side exception on //JSON parsing. Our JSON writer is configured to discard null values. if (!version.isAtLeast(ArtifactoryHttpClient.UNKNOWN_PROPERTIES_TOLERANT_ARTIFACTORY_VERSION)) { buildInfo.setBuildAgent(null); buildInfo.setParentName(null); buildInfo.setParentNumber(null); buildInfo.setVcsRevision(null); } //From Artifactory 2.2.4 we also handle non-numeric build numbers if (!version.isAtLeast(ArtifactoryHttpClient.NON_NUMERIC_BUILD_NUMBERS_TOLERANT_ARTIFACTORY_VERSION)) { String buildNumber = buildInfo.getNumber(); verifyNonNumericBuildNumber(buildNumber); String parentBuildNumber = buildInfo.getParentNumber(); verifyNonNumericBuildNumber(parentBuildNumber); } return toJsonString(buildInfo); } String toJsonString(Object object) throws IOException { JsonFactory jsonFactory = httpClient.createJsonFactory(); StringWriter writer = new StringWriter(); JsonGenerator jsonGenerator = jsonFactory.createJsonGenerator(writer); jsonGenerator.useDefaultPrettyPrinter(); jsonGenerator.writeObject(object); String result = writer.getBuffer().toString(); return result; } private void verifyNonNumericBuildNumber(String buildNumber) { if (buildNumber != null) { try { Long.parseLong(buildNumber); } catch (NumberFormatException e) { throw new IllegalArgumentException( "Cannot handle build/parent build number: " + buildNumber + ". Non-numeric build numbers are supported by Artifactory version " + ArtifactoryHttpClient.NON_NUMERIC_BUILD_NUMBERS_TOLERANT_ARTIFACTORY_VERSION + " and above. Please upgrade your Artifactory or use numeric build numbers."); } } } private ArtifactoryUploadResponse uploadFile(DeployDetails details, String uploadUrl) throws IOException { ArtifactoryUploadResponse response = tryChecksumDeploy(details, uploadUrl); if (response != null) { // Checksum deploy was performed: return response; } HttpPut httpPut = createHttpPutMethod(details, uploadUrl); // add the 100 continue directive httpPut.addHeader(HTTP.EXPECT_DIRECTIVE, HTTP.EXPECT_CONTINUE); FileEntity fileEntity = new FileEntity(details.getFile(), "binary/octet-stream"); response = httpClient.upload(httpPut, fileEntity); int statusCode = response.getStatusLine().getStatusCode(); //Accept both 200, and 201 for backwards-compatibility reasons if ((statusCode != HttpStatus.SC_CREATED) && (statusCode != HttpStatus.SC_OK)) { throw new IOException("Failed to deploy file. Status code: " + statusCode + getMessage(response)); } return response; } private ArtifactoryUploadResponse tryChecksumDeploy(DeployDetails details, String uploadUrl) throws UnsupportedEncodingException { // Try checksum deploy only on file size greater than CHECKSUM_DEPLOY_MIN_FILE_SIZE long fileLength = details.getFile().length(); if (fileLength < CHECKSUM_DEPLOY_MIN_FILE_SIZE) { log.debug("Skipping checksum deploy of file size " + fileLength + " , falling back to regular deployment."); return null; } // Artifactory 2.5.1+ has efficient checksum deployment (checks if the artifact already exists by it's checksum) if (!getArtifactoryVersion().isAtLeast(new ArtifactoryVersion("2.5.1"))) { return null; } HttpPut httpPut = createHttpPutMethod(details, uploadUrl); // activate checksum deploy httpPut.addHeader("X-Checksum-Deploy", "true"); String fileAbsolutePath = details.getFile().getAbsolutePath(); try { ArtifactoryUploadResponse response = httpClient.execute(httpPut); int statusCode = response.getStatusLine().getStatusCode(); //Accept both 200, and 201 for backwards-compatibility reasons if ((statusCode == HttpStatus.SC_CREATED) || (statusCode == HttpStatus.SC_OK)) { log.debug("Successfully performed checksum deploy of file " + fileAbsolutePath + " : " + details.getSha1()); return response; } else { log.debug("Failed checksum deploy of checksum '" + details.getSha1() + "' with statusCode: " + statusCode); } } catch (IOException e) { log.debug("Failed artifact checksum deploy of file " + fileAbsolutePath + " : " + details.getSha1()); } return null; } private HttpPut createHttpPutMethod(DeployDetails details, String uploadUrl) throws UnsupportedEncodingException { StringBuilder deploymentPathBuilder = new StringBuilder().append(uploadUrl); deploymentPathBuilder.append(DeploymentUrlUtils.buildMatrixParamsString(details.getProperties())); HttpPut httpPut = new HttpPut(deploymentPathBuilder.toString()); httpPut.addHeader("X-Checksum-Sha1", details.getSha1()); httpPut.addHeader("X-Checksum-Md5", details.getMd5()); log.debug("Full Artifact Http path: " + httpPut.toString() + "\n@Http Headers: " + Arrays.toString(httpPut.getAllHeaders())); return httpPut; } public void uploadChecksums(DeployDetails details, String uploadUrl) throws IOException { Map<String, String> checksums = getChecksumMap(details); String fileAbsolutePath = details.getFile().getAbsolutePath(); String sha1 = checksums.get("SHA1"); if (StringUtils.isNotBlank(sha1)) { log.debug("Uploading SHA1 for file " + fileAbsolutePath + " : " + sha1); String sha1Url = uploadUrl + ".sha1" + DeploymentUrlUtils.buildMatrixParamsString(details.getProperties()); HttpPut putSha1 = new HttpPut(sha1Url); StringEntity sha1StringEntity = new StringEntity(sha1); ArtifactoryUploadResponse response = httpClient.upload(putSha1, sha1StringEntity); StatusLine sha1StatusLine = response.getStatusLine(); int sha1StatusCode = sha1StatusLine.getStatusCode(); //Accept both 200, and 201 for backwards-compatibility reasons if ((sha1StatusCode != HttpStatus.SC_CREATED) && (sha1StatusCode != HttpStatus.SC_OK)) { throw new IOException("Failed to deploy SHA1 checksum. Status code: " + sha1StatusCode + getMessage(response)); } } String md5 = checksums.get("MD5"); if (StringUtils.isNotBlank(md5)) { log.debug("Uploading MD5 for file " + fileAbsolutePath + " : " + md5); String md5Url = uploadUrl + ".md5" + DeploymentUrlUtils.buildMatrixParamsString(details.getProperties()); HttpPut putMd5 = new HttpPut(md5Url); StringEntity md5StringEntity = new StringEntity(md5); ArtifactoryUploadResponse response = httpClient.upload(putMd5, md5StringEntity); StatusLine md5StatusLine = response.getStatusLine(); int md5StatusCode = md5StatusLine.getStatusCode(); //Accept both 200, and 201 for backwards-compatibility reasons if ((md5StatusCode != HttpStatus.SC_CREATED) && (md5StatusCode != HttpStatus.SC_OK)) { throw new IOException("Failed to deploy MD5 checksum. Status code: " + md5StatusCode + getMessage(response)); } } } private Map<String, String> getChecksumMap(DeployDetails details) throws IOException { Map<String, String> checksums = Maps.newHashMap(); List<String> checksumTypeList = Lists.newArrayList(); if (StringUtils.isBlank(details.getMd5())) { checksumTypeList.add("MD5"); } else { checksums.put("MD5", details.getMd5()); } if (StringUtils.isBlank(details.getSha1())) { checksumTypeList.add("SHA1"); } else { checksums.put("SHA1", details.getSha1()); } if (!checksumTypeList.isEmpty()) { try { checksums.putAll(FileChecksumCalculator.calculateChecksums(details.getFile(), checksumTypeList.toArray(new String[checksumTypeList.size()]))); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } return checksums; } private void throwHttpIOException(String message, StatusLine statusLine) throws IOException { String errorMessage = new StringBuilder(message).append(" HTTP response code: "). append(statusLine.getStatusCode()).append(". HTTP response message: "). append(statusLine.getReasonPhrase()).toString(); throw new IOException(errorMessage); } public ArtifactoryVersion getArtifactoryVersion() { if (artifactoryVersion == null) { try { artifactoryVersion = httpClient.getVersion(); } catch (IOException e) { artifactoryVersion = ArtifactoryVersion.NOT_FOUND; } } return artifactoryVersion; } /** * Returns the response message * * @param response from Artifactory * @return the response message String. */ private String getMessage(ArtifactoryUploadResponse response) { String responseMessage = ""; if (response.getErrors() != null) { responseMessage = getResponseMessage(response.getErrors()); if (StringUtils.isNotBlank(responseMessage)) { responseMessage = " Response message: " + responseMessage; } } return responseMessage; } /** * @param errorList errors list returned from Artifactory. * @return response message string. */ private String getResponseMessage(List<ArtifactoryUploadResponse.Error> errorList) { if (errorList == null || errorList.isEmpty()) { return ""; } StringBuilder builder = new StringBuilder("Artifactory returned the following errors: "); for (ArtifactoryUploadResponse.Error error : errorList) { builder.append("\n"). append(error.getMessage()). append(" Status code: "). append(error.getStatus()); } return builder.toString(); } /** * @param entity the entity to retrive the message from. * @return response entity content. * @throws IOException */ private String getMessageFromEntity(HttpEntity entity) throws IOException { String responseMessage = ""; if (entity != null) { responseMessage = getResponseEntityContent(entity); EntityUtils.consume(entity); if (StringUtils.isNotBlank(responseMessage)) { responseMessage = " Response message: " + responseMessage; } } return responseMessage; } /** * Returns the response entity content * * @param responseEntity the response entity * @return response entity content * @throws IOException */ private String getResponseEntityContent(HttpEntity responseEntity) throws IOException { InputStream in = responseEntity.getContent(); if (in != null) { return IOUtils.toString(in, "UTF-8"); } return ""; } }