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

com.capitalone.dashboard.collector.DefaultArtifactoryClient Maven / Gradle / Ivy

The newest version!
package com.capitalone.dashboard.collector;

import com.capitalone.dashboard.client.RestClient;
import com.capitalone.dashboard.model.ArtifactItem;
import com.capitalone.dashboard.model.ArtifactoryRepo;
import com.capitalone.dashboard.model.BaseArtifact;
import com.capitalone.dashboard.model.BinaryArtifact;
import com.capitalone.dashboard.model.Collector;
import com.capitalone.dashboard.model.RepoAndPattern;
import com.capitalone.dashboard.model.ServerSetting;
import com.capitalone.dashboard.repository.BinaryArtifactRepository;
import com.capitalone.dashboard.util.ArtifactUtil;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.map.HashedMap;
import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.lang3.StringUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.RestClientException;

import java.nio.charset.StandardCharsets;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
public class DefaultArtifactoryClient implements ArtifactoryClient {
	public static final int UPPER_INDEX = -1;
	public static final String SLASH = "/";
	private static final Logger LOGGER = LoggerFactory.getLogger(DefaultArtifactoryClient.class);

	private static final String REPOS_URL_SUFFIX = "api/repositories";
	private static final String AQL_URL_SUFFIX = "api/search/aql";

	private final DateFormat FULL_DATE = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");

	private final ArtifactorySettings artifactorySettings;
	private final RestClient restClient;

	private final List artifactPatterns;

	private final BinaryArtifactRepository binaryArtifactRepository;

	@Autowired
	public DefaultArtifactoryClient(ArtifactorySettings artifactorySettings, RestClient restClient, BinaryArtifactRepository binaryArtifactRepository) {
		this.artifactorySettings = artifactorySettings;
		this.restClient = restClient;
		this.binaryArtifactRepository = binaryArtifactRepository;
		this.artifactPatterns = new ArrayList<>();

		if (artifactorySettings.getServers() != null) {
			for (String str : getPatterns()) {
				try {
					Pattern p = Pattern.compile(str);

					LOGGER.info("Adding Pattern " + p.pattern());

					artifactPatterns.add(p);
				} catch (PatternSyntaxException e) {
					LOGGER.error("Invalid pattern: " + e.getMessage());
					throw e;
				}
			}
		}

		if (artifactPatterns.isEmpty()) {
			throw new IllegalStateException("No valid artifact patterns configured. Aborting.");
		}
	}

	private List getPatterns(){
		List patterns = new ArrayList<>();
		if(artifactorySettings.getServers()!=null) {
			artifactorySettings.getServers().forEach(serverSetting ->
					serverSetting.getRepoAndPatterns().forEach(repoAndPattern -> patterns.addAll(repoAndPattern.getPatterns())));
		}
		return patterns;
	}

	public List getRepos(String instanceUrl) {
		List result = new ArrayList<>();
		ResponseEntity responseEntity = makeRestCall(instanceUrl, REPOS_URL_SUFFIX);
		String returnJSON = responseEntity.getBody();
		JSONParser parser = new JSONParser();

		try {
			JSONArray jsonRepos = (JSONArray) parser.parse(returnJSON);

			for (Object repo : jsonRepos) {
				JSONObject jsonRepo = (JSONObject) repo;

				final String repoName = getString(jsonRepo, "key");
				final String repoURL = getString(jsonRepo, "url");
				LOGGER.debug("repoName:" + repoName);
				LOGGER.debug("repoURL: " + repoURL);
				ArtifactoryRepo artifactoryRepo = new ArtifactoryRepo();
				artifactoryRepo.setInstanceUrl(instanceUrl);
				artifactoryRepo.setRepoName(repoName);
				artifactoryRepo.setRepoUrl(repoURL);

				// add the repo
				result.add(artifactoryRepo);
			}
		} catch (ParseException e) {
			LOGGER.error("Parsing repos on instance: " + instanceUrl, e);
		}

		return result;
	}

	public List getArtifactItems(String instanceUrl, String repoName,String pattern, long lastUpdated) {
		LOGGER.info("Last collector update=" + FULL_DATE.format(new Date(lastUpdated)));
		List baseArtifacts = new ArrayList<>();
		if (StringUtils.isNotEmpty(instanceUrl) && StringUtils.isNotEmpty(repoName)) {
			long currentTime = System.currentTimeMillis();
			// unit of time's worth of data
			TimeUnit unitTime = TimeUnit.valueOf(artifactorySettings.getTimeUnit());
			long timeInterval = unitTime.toMillis(1);
			// lookback time
			long lookback = artifactorySettings.getTimeInterval();
			// if lastUpdated is more than 'lookback' days, then set it to 'lookback'
			if (lastUpdated < (currentTime - unitTime.toMillis(lookback))) {
				LOGGER.info("Lookback period is -- " + lookback + " " + unitTime.toString());
				lastUpdated = currentTime - unitTime.toMillis(lookback);
			}

			for (long startTime = lastUpdated; startTime < currentTime; startTime += timeInterval) {
				String body = "items.find({\"created\" : {\"$gt\" : \"" + FULL_DATE.format(new Date(startTime))
						+ "\"}, \"created\" : {\"$lte\" : \"" + FULL_DATE.format(new Date(Math.min(startTime + timeInterval, currentTime)))
						+ "\"},\"repo\":{\"$eq\":\"" + repoName
						+ "\"}}).include(\"*\")";
				LOGGER.info("Artifact Query ==> " + body);
				ResponseEntity responseEntity = makeRestPost(instanceUrl, AQL_URL_SUFFIX, MediaType.TEXT_PLAIN, body);
				String returnJSON = responseEntity.getBody();
				JSONParser parser = new JSONParser();
				try {
					JSONObject json = (JSONObject) parser.parse(returnJSON);
					JSONArray jsonArtifacts = getJsonArray(json, "results");
					LOGGER.info("Total JSON Artifacts -- " + jsonArtifacts.size());
					int count =0;
					for (Object artifact : jsonArtifacts) {
						JSONObject jsonArtifact = (JSONObject) artifact;
						BaseArtifact baseArtifact = new BaseArtifact();

						String repo = getString(jsonArtifact, "repo");
						final String artifactCanonicalName = getString(jsonArtifact, "name");
						String artifactPath = getString(jsonArtifact, "path");
						String fullPath = artifactPath + "/" + artifactCanonicalName;

						try {
							Pattern p = Pattern.compile(pattern);
							BinaryArtifact result = ArtifactUtil.parse(p, fullPath);

							String artName = "";
							String artPath = artifactPath;
							if (result != null) {
								artName = result.getArtifactName();
								artPath = result.getArtifactGroupId() + "/" + result.getArtifactName();
							}

							if (artifactPath.charAt(artifactPath.length() - 1) == '/') {
								artifactPath = artifactPath.substring(0, artifactPath.length() - 1);
							}

							// create artifact_items (collector_item)
							ArtifactItem artifactItem = createArtifactItem(instanceUrl, repo, artName, artPath);

							String sTimestamp = getString(jsonArtifact, "modified");
							if (sTimestamp == null) {
								sTimestamp = getString(jsonArtifact, "created");
							}
							long timestamp = 0;
							if (sTimestamp != null) {
								try {
									Date date = FULL_DATE.parse(sTimestamp);
									timestamp = date.getTime();
								} catch (java.text.ParseException e) {
									LOGGER.error("Parsing artifact timestamp: " + sTimestamp, e);
								}
							}

							// find existing base artifact matching artifact item unique options
							BaseArtifact suspect = baseArtifacts.stream().filter(b ->
									StringUtils.equalsIgnoreCase(b.getArtifactItem().getArtifactName(), artifactItem.getArtifactName())
											&& StringUtils.equalsIgnoreCase(b.getArtifactItem().getRepoName(), artifactItem.getRepoName())
											&& StringUtils.equalsIgnoreCase(b.getArtifactItem().getPath(), artifactItem.getPath())).findFirst().orElse(baseArtifact);

							// create artifactInfo
							List bas = createArtifactForArtifactBased(artifactCanonicalName, artifactPath, timestamp, jsonArtifact);
							if (CollectionUtils.isNotEmpty(bas)) {
								insertOrUpdateBaseArtifact(baseArtifacts, artifactItem, suspect, bas);
							}
						} catch (Exception e) {
							LOGGER.error("Received Exception= " + e.getMessage() + " artifactPath=" + artifactPath, e);
						}
						count++;
						LOGGER.info("artifact count -- " + count + " repo=" + repoName + "  artifactPath=" + artifactPath);
					}
				} catch (ParseException e) {
					LOGGER.error("Parsing artifact items on instance: " + instanceUrl + " and repo: " + repoName, e);
				}
			}
		}
		return baseArtifacts;
	}

	public Map> getLatestBinaryArtifacts(Collector collector,List patterns, String instanceUrl, String repo){
		long start = getLastUpdated(collector.getLastExecuted());
		Map> processing = new HashMap<>();
		try {
			JSONArray binaryArtifacts = sendPostAll(start,repo,instanceUrl);
			if(CollectionUtils.isNotEmpty(binaryArtifacts)) {
				for (Object binaryArtifact : binaryArtifacts) {
					JSONObject baObject = (JSONObject) binaryArtifact;
					final String artifactCanonicalName = getString(baObject, "name");
					String artifactPath = getString(baObject, "path");
					String fullPath = artifactPath + "/" + artifactCanonicalName;
					boolean isValidParse;
					BinaryArtifact parsedResult = new BinaryArtifact();
					for (String pattern : patterns) {
						Pattern p = Pattern.compile(pattern);
						isValidParse = ArtifactUtil.validParse(parsedResult, p, fullPath);
						if (isValidParse) break;
					}
					List artifacts = new ArrayList<>();
					String path = parsedResult.getArtifactGroupId() + "/" + parsedResult.getArtifactName();
					ArtifactItem artifactItem = new ArtifactItem(repo, parsedResult.getArtifactName(), path, instanceUrl);
					BinaryArtifact artifact = createBinaryArtifactFromJsonArtifact(baObject, artifactItem);
					artifact.setArtifactGroupId(parsedResult.getArtifactGroupId());
					artifact.setArtifactModule(parsedResult.getArtifactModule());
					artifact.setArtifactVersion(parsedResult.getArtifactVersion());
					artifact.setArtifactName(parsedResult.getArtifactName());
					artifact.setArtifactClassifier(parsedResult.getArtifactClassifier());
					artifact.setArtifactExtension(parsedResult.getArtifactExtension());
					artifacts.add(artifact);
					processing.merge(artifactItem, artifacts, (existing, incoming) -> Stream.of(existing, incoming).flatMap(Collection::stream).collect(Collectors.toList()));
				}
			}
		} catch (ParseException e) {
			LOGGER.error("Error occurred while parsing Binary artifacts=", e.getMessage());
		}
		return processing;
	}
	public List getArtifactsForVersion(ArtifactItem artifactItem, String version, long startTime, List patterns){
		List binaryArtifacts = new ArrayList<>();
		normalize(artifactItem);
		int count =0;

		try {
			JSONArray jsonArtifacts = sendPost(startTime,
					artifactItem.getRepoName(),
					artifactItem.getPath(),
					artifactItem.getInstanceUrl());
			if (Objects.isNull(jsonArtifacts)) {
				LOGGER.error("No json artifacts found for repo=" + artifactItem.getRepoName()
						+ " path=" + artifactItem.getPath()
						+ " collectorItemId=" + artifactItem.getId());
				return binaryArtifacts;
			}

			LOGGER.info("Total JSON Artifacts -- " + jsonArtifacts.size());
			for (Object artifact : jsonArtifacts) {
				JSONObject jsonArtifact = (JSONObject) artifact;
				BinaryArtifact newbinaryArtifact = createBinaryArtifactFromJsonArtifact(jsonArtifact, artifactItem);
				final String artifactCanonicalName = getString(jsonArtifact, "name");
				String artifactPath = getString(jsonArtifact, "path");
				String fullPath = artifactPath + "/" + artifactCanonicalName;

				BinaryArtifact parsedResult = new BinaryArtifact();
				boolean isValidParse = false;
				// check if have values for all regex groups in pattern
				// try each pattern, if all values are found, then break loop; otherwise continue onto next pattern
				for (String pattern: patterns) {
					Pattern p = Pattern.compile(pattern);
					isValidParse = ArtifactUtil.validParse(parsedResult, p, fullPath);
					if (isValidParse) break;
				}
				if (isValidParse) {
					// version null check
					if (parsedResult.getArtifactVersion() == null) {
						LOGGER.error("Could not find version for repo=" + artifactItem.getRepoName() + " fullPath=" + fullPath);
						break;
					}
					if(parsedResult.getArtifactVersion().equalsIgnoreCase(version)){
						newbinaryArtifact = updateBinaryArtifactWithPatternMatchedAttributes(newbinaryArtifact, parsedResult);
						// Check if matching Binary Artifact already exists
						BinaryArtifact existingBinaryArtifact = binaryArtifactRepository.findTopByCollectorItemIdAndArtifactVersionOrderByTimestampDesc(artifactItem.getId(),
								newbinaryArtifact.getArtifactVersion());
						if (Objects.nonNull(existingBinaryArtifact)) {
							// update existing binary artifact for that version and update timestamp
							updateExistingBinaryArtifact(newbinaryArtifact, existingBinaryArtifact);
							binaryArtifacts.add(newbinaryArtifact);
							binaryArtifactRepository.save(newbinaryArtifact);
						}
						else {
							// get latest binary artifact for this artifact item with build info
							attachLatestBuildInfo(artifactItem, newbinaryArtifact);
							// save immediately to avoid creating multiple new BAs for same collectorItemId and artifactVersion
							binaryArtifactRepository.save(newbinaryArtifact);
						}
						count++;
						LOGGER.info("json artifact count -- " + count
								+ " repo=" + artifactItem.getRepoName()
								+ ", artifactPath=" + artifactPath
								+ ", artifactCanonicalName=" + artifactCanonicalName
								+ ", collectorItemId=" + artifactItem.getId()+", artifactVersion="+version);
					}
				} else {
					// invalid parse/not enough data found
					count++;
					LOGGER.error("Not enough data found for json artifact count -- " + count
							+ " repo=" + artifactItem.getRepoName()
							+ " artifactPath=" + artifactPath
							+ " artifactCanonicalName=" + artifactCanonicalName
							+ " collectorItemId=" + artifactItem.getId()+", artifactVersion="+version);
				}
			}

		} catch (ParseException e) {
			LOGGER.error("Parsing artifact items on instance: " + artifactItem.getInstanceUrl() + " and repo: " + artifactItem.getRepoName(), e);
		} catch (Exception e) {
			LOGGER.error("Received Exception= " + e.toString() + " artifactPath=" + artifactItem.getPath(), e);
		}
		return binaryArtifacts;
	}


	public List getArtifacts(ArtifactItem artifactItem,List patterns){
        long start = getLastUpdated(artifactItem.getLastUpdated());
		List binaryArtifacts = new ArrayList<>();

		try {
			JSONArray jsonArtifacts = sendPost(start,
					artifactItem.getRepoName(),
					artifactItem.getPath(),
					artifactItem.getInstanceUrl());
			if (Objects.isNull(jsonArtifacts)) {
				LOGGER.error("No json artifacts found for repo=" + artifactItem.getRepoName()
						+ " path=" + artifactItem.getPath()
						+ " collectorItemId=" + artifactItem.getId());
				return binaryArtifacts;
			}
			LOGGER.info("Total JSON Artifacts -- " + jsonArtifacts.size());
			int count = 0;
			for (Object artifact : jsonArtifacts) {
				JSONObject jsonArtifact = (JSONObject) artifact;
				BinaryArtifact newbinaryArtifact = createBinaryArtifactFromJsonArtifact(jsonArtifact, artifactItem);
				final String artifactCanonicalName = getString(jsonArtifact, "name");
				String artifactPath = getString(jsonArtifact, "path");
				String fullPath = artifactPath + "/" + artifactCanonicalName;

				BinaryArtifact parsedResult = new BinaryArtifact();
				boolean isValidParse = false;
				// check if have values for all regex groups in pattern
				// try each pattern, if all values are found, then break loop; otherwise continue onto next pattern
				for (String pattern: patterns) {
					Pattern p = Pattern.compile(pattern);
					isValidParse = ArtifactUtil.validParse(parsedResult, p, fullPath);
					if (isValidParse) break;
				}
				if (isValidParse) {
					// version null check
					if (parsedResult.getArtifactVersion() == null) {
						LOGGER.error("Could not find version for repo=" + artifactItem.getRepoName() + " fullPath=" + fullPath);
						break;
					}
					newbinaryArtifact = updateBinaryArtifactWithPatternMatchedAttributes(newbinaryArtifact, parsedResult);
					// Check if matching Binary Artifact already exists
					BinaryArtifact existingBinaryArtifact = binaryArtifactRepository.findTopByCollectorItemIdAndArtifactVersionOrderByTimestampDesc(artifactItem.getId(),
							newbinaryArtifact.getArtifactVersion());
					if (Objects.nonNull(existingBinaryArtifact)) {
						// update existing binary artifact for that version and update timestamp
						updateExistingBinaryArtifact(newbinaryArtifact, existingBinaryArtifact);
						binaryArtifacts.add(newbinaryArtifact);
						binaryArtifactRepository.save(newbinaryArtifact);
					} else {
						// get latest binary artifact for this artifact item with build info
						attachLatestBuildInfo(artifactItem, newbinaryArtifact);
						// save immediately to avoid creating multiple new BAs for same collectorItemId and artifactVersion
						binaryArtifactRepository.save(newbinaryArtifact);
					}

					count++;
					LOGGER.info("json artifact count -- " + count
							+ " repo=" + artifactItem.getRepoName()
							+ " artifactPath=" + artifactPath
							+ " artifactCanonicalName=" + artifactCanonicalName
							+ " collectorItemId=" + artifactItem.getId());
				} else {
					// invalid parse/not enough data found
					count++;
					LOGGER.error("Not enough data found for json artifact count -- " + count
							+ " repo=" + artifactItem.getRepoName()
							+ " artifactPath=" + artifactPath
							+ " artifactCanonicalName=" + artifactCanonicalName
							+ " collectorItemId=" + artifactItem.getId());
				}
			}

		} catch (ParseException e) {
			LOGGER.error("Parsing artifact items on instance: " + artifactItem.getInstanceUrl() + " and repo: " + artifactItem.getRepoName(), e);
		} catch (Exception e) {
			LOGGER.error("Received Exception= " + e.toString() + " artifactPath=" + artifactItem.getPath(), e);
		}
		return binaryArtifacts;
	}

	private long getLastUpdated(long lastUpdated) {
		if(lastUpdated == 0) {
			return System.currentTimeMillis() - artifactorySettings.getOffSet();
		} else{
			// unit of time's worth of data
			TimeUnit unitTime = TimeUnit.valueOf(artifactorySettings.getTimeUnit());
			// lookback time
			long lookback = artifactorySettings.getTimeInterval();
			long currentTime = System.currentTimeMillis();
			// if lastUpdated is more than 'lookback' days, then set it to 'lookback'
			if (lastUpdated < (currentTime - unitTime.toMillis(lookback))) {
				LOGGER.info("Lookback period is -- " + lookback + " " + unitTime.toString());
				lastUpdated = currentTime - unitTime.toMillis(lookback);
			}
			return lastUpdated - artifactorySettings.getOffSet();
		}
	}

	public List getPattern(String repoName){
		if(Objects.isNull(repoName)) return null;
		List pattern =  getRepoAndSubRepoPatterns().entrySet().stream().filter(entry -> repoName.contains(entry.getKey())).map(entry -> entry.getValue()).findFirst().orElse(null);
		if (org.springframework.util.CollectionUtils.isEmpty(pattern)) return null;
		return pattern;
	}

	@Override
	public ArtifactItem normalize(ArtifactItem artifactItem){
		artifactItem.setInstanceUrl(removeLeadAndTrailingSlash(artifactItem.getInstanceUrl()));
		artifactItem.setArtifactName(removeLeadAndTrailingSlash(artifactItem.getArtifactName()));
		artifactItem.setRepoName(truncate(artifactItem.getRepoName()));
		artifactItem.setPath(normalizePath(artifactItem.getPath(),artifactItem.getRepoName()));
		return  artifactItem;
	}

	private Map> getRepoAndSubRepoPatterns() {
		Map> patterns = new HashedMap();
		artifactorySettings.getServers().forEach(serverSetting -> {
			patterns.putAll(getRepoAndPatternsForServ(serverSetting.getRepoAndPatterns()));
			patterns.putAll(getSubRepoPatternsForServ(serverSetting.getRepoAndPatterns()));
		});
		return patterns;
	}

	private Map> getRepoAndPatternsForServ(List repoAndPatterns) {
		return repoAndPatterns.stream().collect(Collectors.toMap(RepoAndPattern::getRepo, RepoAndPattern::getPatterns));
	}

	private Map> getSubRepoPatternsForServ(List repoAndPatterns) {
		Map> subRepoToPattern = new HashMap<>();
		Map, List> subReposListToPatterns = repoAndPatterns.stream()
				.filter(repoAndPattern -> !org.springframework.util.CollectionUtils.isEmpty(repoAndPattern.getSubRepos()))
				.collect(Collectors.toMap(RepoAndPattern::getSubRepos, RepoAndPattern::getPatterns));
		subReposListToPatterns.forEach((subRepos, patterns) -> subRepos.forEach(subRepo -> subRepoToPattern.put(subRepo, patterns)));
		return subRepoToPattern;
	}


	private JSONArray sendPost(long start, String repoName, String path, String instanceUrl) throws ParseException {
		String returnJSON = sendPostQueryByRepo(start, repoName, path, instanceUrl);
		if (Objects.isNull(returnJSON)) return null;
		JSONParser parser = new JSONParser();
		JSONArray jsonArtifacts = parseJsonArtifacts(parser, returnJSON);
		if (!jsonArtifacts.isEmpty()) return jsonArtifacts;
		return null;
	}

	private JSONArray sendPostAll(long start, String repoName, String instanceUrl) throws ParseException {
		String returnJSON = sendPostQueryAll(start, repoName, instanceUrl);
		if (Objects.isNull(returnJSON)) return null;
		JSONParser parser = new JSONParser();
		JSONArray jsonArtifacts = parseJsonArtifacts(parser, returnJSON);
		if (!jsonArtifacts.isEmpty()) return jsonArtifacts;
		return null;
	}

	private String sendPostQueryAll(long start, String repo, String instanceUrl) {
		String query = buildQueryAll(start, repo);
		LOGGER.info("Artifact Query ==> " + query);
		ResponseEntity responseEntity = makeRestPost(instanceUrl, AQL_URL_SUFFIX, MediaType.TEXT_PLAIN, query);
		// retry if first time fails
		if (Objects.isNull(responseEntity)) {
			responseEntity = makeRestPost(instanceUrl, AQL_URL_SUFFIX, MediaType.TEXT_PLAIN, query);
		}
		if (Objects.isNull(responseEntity)) return null;
		return responseEntity.getBody();
	}

	private String buildQueryAll(long start, String repo){
		String query =  "items.find({\"created\" : {\"$gt\" : \"" + FULL_DATE.format(new Date(start))
				+ "\"},\"repo\":{\"$eq\":\"" + repo
				+ "\"}})"
				+ ".include(\"*\")"
				+ ".sort({\"$asc\" : [\"modified\"]})";
		return query;

	}

	private String sendPostQueryByRepo(long start, String repo, String path, String instanceUrl) {
		String query = buildQuery(start, repo, path);
		LOGGER.info("Artifact Query ==> " + query);
		ResponseEntity responseEntity = makeRestPost(instanceUrl, AQL_URL_SUFFIX, MediaType.TEXT_PLAIN, query);
		// retry if first time fails
		if (Objects.isNull(responseEntity)) {
			responseEntity = makeRestPost(instanceUrl, AQL_URL_SUFFIX, MediaType.TEXT_PLAIN, query);
		}
		if (Objects.isNull(responseEntity)) return null;
		return responseEntity.getBody();
	}

	private String buildQuery(long start, String repo, String path){
		String constructPath = path + "/*";
		String query =  "items.find({\"created\" : {\"$gt\" : \"" + FULL_DATE.format(new Date(start))
                + "\"},\"repo\":{\"$eq\":\"" + repo
				+ "\"},\"path\":{\"$match\":\""+constructPath+"\"}})"
				+ ".include(\"*\")"
				+ ".sort({\"$asc\" : [\"modified\"]})";
		return query;

	}

	private JSONArray parseJsonArtifacts(JSONParser parser, String returnJSON) throws ParseException {
		JSONObject json = (JSONObject) parser.parse(returnJSON);
		JSONArray jsonArtifacts = getJsonArray(json, "results");
		return jsonArtifacts;
	}

	private BinaryArtifact createBinaryArtifactFromJsonArtifact(JSONObject jsonArtifact, ArtifactItem artifactItem) {
		BinaryArtifact binaryArtifact = new BinaryArtifact();
		binaryArtifact.setCollectorItemId(artifactItem.getId());
		binaryArtifact.setRepo(getString(jsonArtifact, "repo"));
		binaryArtifact.setPath(getString(jsonArtifact, "path"));
		binaryArtifact.setCanonicalName(getString(jsonArtifact, "name"));
		binaryArtifact.setType(getString(jsonArtifact, "type"));
		binaryArtifact.setCreatedTimeStamp(convertTimestamp(getString(jsonArtifact, "created")));
		binaryArtifact.setCreatedBy(getString(jsonArtifact, "created_by"));
		binaryArtifact.setModifiedTimeStamp(convertTimestamp(getString(jsonArtifact, "modified")));
		binaryArtifact.setModifiedBy(getString(jsonArtifact, "modified_by"));
		binaryArtifact.setActual_md5(getString(jsonArtifact, "actual_md5"));
		binaryArtifact.setActual_sha1(getString(jsonArtifact, "actual_sha1"));
		binaryArtifact.setVirtualRepos(getJsonArray(jsonArtifact, "virtual_repos"));
		binaryArtifact.setTimestamp(System.currentTimeMillis());

		return binaryArtifact;
	}

	private BinaryArtifact updateBinaryArtifactWithPatternMatchedAttributes(BinaryArtifact binaryArtifact, BinaryArtifact result) {
		binaryArtifact.setArtifactName(result.getArtifactName());
		binaryArtifact.setArtifactGroupId(result.getArtifactGroupId()) ;
		binaryArtifact.setArtifactVersion(result.getArtifactVersion());
		binaryArtifact.setArtifactExtension(result.getArtifactExtension());
		binaryArtifact.setArtifactModule(result.getArtifactModule());
		binaryArtifact.setArtifactClassifier(result.getArtifactClassifier());

		return binaryArtifact;
	}

	private void updateExistingBinaryArtifact(BinaryArtifact newBinaryArtifact, BinaryArtifact existingBinaryArtifact) {
		if(!CollectionUtils.isEmpty(existingBinaryArtifact.getBuildInfos())){
			newBinaryArtifact.setBuildInfos(existingBinaryArtifact.getBuildInfos());
		}
	}


	private void attachLatestBuildInfo(ArtifactItem artifactItem, BinaryArtifact binaryArtifact) {
		// get latest binary artifact associated with the artifact item by desc timestamp
		BinaryArtifact latestWithBuildInfo = binaryArtifactRepository.findTopByCollectorItemIdAndBuildInfosIsNotEmptyOrderByTimestampDesc(artifactItem.getId(), new Sort(Sort.Direction.DESC, "timestamp"));
		if (Objects.isNull(latestWithBuildInfo)) return;
		binaryArtifact.setBuildInfos(latestWithBuildInfo.getBuildInfos());
	}

	private void insertOrUpdateBaseArtifact(List baseArtifacts, ArtifactItem artifactItem, BaseArtifact suspect, List bas) {
		for (BinaryArtifact ba: bas) {
			if(containsBinaryArtifactWithBuildInfo(suspect, ba)){
				if(baseArtifactNotNull(baseArtifacts, suspect)){
					addOrUpdateBinaryArtifactToBaseArtifact(baseArtifacts, artifactItem, suspect, ba);
				}else{
					addNewBaseArtifact(baseArtifacts, artifactItem, suspect, ba);
				}
			}

		}
	}


	private void addNewBaseArtifact(List baseArtifacts, ArtifactItem artifactItem, BaseArtifact suspect, BinaryArtifact ba) {
		suspect.getBinaryArtifacts().add(ba);
		suspect.setArtifactItem(artifactItem);
		baseArtifacts.add(suspect);
	}

	private void addOrUpdateBinaryArtifactToBaseArtifact(List baseArtifacts, ArtifactItem artifactItem, BaseArtifact suspect, BinaryArtifact ba) {
		int index = baseArtifacts.indexOf(suspect);
		if (index > UPPER_INDEX) {
			updateArtifactsInExistingBaseArtifact(baseArtifacts, ba, index);
		} else {
			addNewBaseArtifact(baseArtifacts,artifactItem,suspect,ba);
		}
	}

	private void updateArtifactsInExistingBaseArtifact(List baseArtifacts, BinaryArtifact ba, int index) {
		int ind = baseArtifacts.get(index).getBinaryArtifacts().indexOf(ba);
		if(ind > UPPER_INDEX){
			baseArtifacts.get(index).getBinaryArtifacts().set(ind,ba);
		}else{
			baseArtifacts.get(index).getBinaryArtifacts().add(ba);
		}
	}

	private boolean baseArtifactNotNull(List baseArtifacts, BaseArtifact suspect) {
		return baseArtifacts!= null && suspect.getArtifactItem()!=null && !baseArtifacts.isEmpty();
	}

	private boolean containsBinaryArtifactWithBuildInfo(BaseArtifact suspect, BinaryArtifact ba) {
		return isNewData(suspect, ba) || artifactWithBuildData(suspect, ba);
	}

	private boolean artifactWithBuildData(BaseArtifact suspect, BinaryArtifact ba) {
		return suspect.getBinaryArtifacts().contains(ba) && CollectionUtils.isNotEmpty(ba.getBuildInfos());
	}

	private boolean isNewData(BaseArtifact suspect, BinaryArtifact ba) {
		return !suspect.getBinaryArtifacts().contains(ba);
	}


	public List getArtifacts(String instanceUrl, String repoName, long lastUpdated) {
		List result = new ArrayList<>();
		// get the list of artifacts
		if (StringUtils.isNotEmpty(instanceUrl) && StringUtils.isNotEmpty(repoName)) {
			String body = "items.find({\"created\" : {\"$gt\" : \"" + FULL_DATE.format(new Date(lastUpdated))
					+ "\"},\"repo\":{\"$eq\":\"" + repoName
					+ "\"}}).include(\"repo\", \"name\", \"path\", \"created\", \"modified\", \"property\")";

			ResponseEntity responseEntity = makeRestPost(instanceUrl, AQL_URL_SUFFIX, MediaType.TEXT_PLAIN, body);
			String returnJSON = responseEntity.getBody();
			JSONParser parser = new JSONParser();

			try {
				JSONObject json = (JSONObject) parser.parse(returnJSON);
				JSONArray jsonArtifacts = getJsonArray(json, "results");
				for (Object artifact : jsonArtifacts) {
					JSONObject jsonArtifact = (JSONObject) artifact;

					final String artifactCanonicalName = getString(jsonArtifact, "name");
					String artifactPath = getString(jsonArtifact, "path");
					if (artifactPath.charAt(artifactPath.length()-1) == '/') {
						artifactPath = artifactPath.substring(0, artifactPath.length()-1);
					}
					String sTimestamp = getString(jsonArtifact, "modified");
					if (sTimestamp == null) {
						sTimestamp = getString(jsonArtifact, "created");
					}
					long timestamp = 0;
					if (sTimestamp != null) {
						try {
							Date date = FULL_DATE.parse(sTimestamp);
							timestamp = date.getTime();
						} catch (java.text.ParseException e) {
							LOGGER.error("Parsing artifact timestamp: " + sTimestamp, e);
						}
					}
					BinaryArtifact ba = createArtifact(artifactCanonicalName, artifactPath, timestamp, jsonArtifact);
					if (ba != null) {
						result.add(ba);
					}
				}
			} catch (ParseException e) {
				LOGGER.error("Parsing artifacts on instance: " + instanceUrl + " and repo: " + repoName, e);
			}
		}

		return result;
	}


	/**
	 * Creates an artifact given its canonical name and path.
	 * Artifacts are created by supplied pattern configurations. By default three are supplied:
	 * 1. Maven artifacts:
	 * 		[org]/[module]/[version]/[module]-[version]([-classifier])(.[ext])
	 * 2. Ivy artifacts:
	 * 		(a) [org]/[module]/[revision]/[type]/[artifact]-[revision](-[classifier])(.[ext])
	 * 		(b) [org]/[module]/[revision]/ivy-[revision](-[classifier]).xml
	 *
	 * Using these patterns, we extract the artifact name, version and group id from the canonical name and path.
	 *
	 * @param artifactCanonicalName			artifact's canonical name in artifactory
	 * @param artifactPath					artifact's path in artifactory
	 * @param timestamp						the artifact's timestamp
	 * @param jsonArtifact 					the artifact metadata is extracted from here
	 * @return
	 */
	private BinaryArtifact createArtifact(String artifactCanonicalName, String artifactPath, long timestamp, JSONObject jsonArtifact) {
		BinaryArtifact result = null;
		String fullPath = artifactPath + "/" + artifactCanonicalName;

		int idx = 0;
		for (Pattern pattern : artifactPatterns) {
			result = ArtifactUtil.parse(pattern, fullPath);

			if (result != null) {
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("Artifact at " + fullPath + " matched pattern " + idx);
				}

				result.setType(getString(jsonArtifact, "type"));
				result.setCreatedTimeStamp(convertTimestamp(getString(jsonArtifact, "created")));
				result.setCreatedBy(getString(jsonArtifact, "created_by"));
				result.setModifiedTimeStamp(convertTimestamp(getString(jsonArtifact, "modified")));
				result.setModifiedBy(getString(jsonArtifact, "modified_by"));
				result.setActual_md5(getString(jsonArtifact,  "actual_md5"));
				result.setActual_sha1(getString(jsonArtifact, "actual_sha1"));
				result.setCanonicalName(artifactCanonicalName);
				result.setTimestamp(timestamp);
				result.setVirtualRepos(getJsonArray(jsonArtifact, "virtual_repos"));
				addMetadataToArtifact(result, jsonArtifact);


				return result;
			}

			idx++;
		}

		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("Artifact at " + fullPath + " did not match any patterns.");
		}
		return null;
	}

	private List createArtifactForArtifactBased(String artifactCanonicalName, String artifactPath, long timestamp, JSONObject jsonArtifact) {
		BinaryArtifact result = null;
		String fullPath = artifactPath + "/" + artifactCanonicalName;
		List binaryArtifactList = new ArrayList<>();
		int idx = 0;
		for (Pattern pattern : artifactPatterns) {
			result = ArtifactUtil.parse(pattern, fullPath);
			if (result != null) {
				String artifactName = result.getArtifactName();
				String artifactVersion = result.getArtifactVersion();
				Iterable bas = binaryArtifactRepository.findByArtifactNameAndArtifactVersion(artifactName, artifactVersion);
				if(!IterableUtils.isEmpty(bas)){
					for (BinaryArtifact ba: bas) {
						setCollectorItemId(result, ba);
						setBuilds(result, ba);
						binaryArtifactRepository.delete(ba.getId());
					}
				}
				result.setType(getString(jsonArtifact, "type"));
				result.setCreatedTimeStamp(convertTimestamp(getString(jsonArtifact, "created")));
				result.setCreatedBy(getString(jsonArtifact, "created_by"));
				result.setModifiedTimeStamp(convertTimestamp(getString(jsonArtifact, "modified")));
				result.setModifiedBy(getString(jsonArtifact, "modified_by"));
				result.setActual_md5(getString(jsonArtifact, "actual_md5"));
				result.setActual_sha1(getString(jsonArtifact, "actual_sha1"));
				result.setCanonicalName(artifactCanonicalName);
				result.setTimestamp(timestamp);
				result.setVirtualRepos(getJsonArray(jsonArtifact, "virtual_repos"));
				addMetadataToArtifact(result, jsonArtifact);

				binaryArtifactList.add(result);
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("Artifact at " + fullPath + " matched pattern " + idx);
				}
			}
			idx++;
		}

		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("Artifact at " + fullPath + " did not match any patterns.");
		}
		return binaryArtifactList;
	}

	private void setBuilds(BinaryArtifact result, BinaryArtifact ba) {
		if(Objects.nonNull(ba.getBuildInfos())&& !ba.getBuildInfos().isEmpty()){
			result.setBuildInfos(ba.getBuildInfos());
		}
	}

	private void setCollectorItemId(BinaryArtifact result, BinaryArtifact ba) {
		if(ba.getCollectorItemId()!=null){
			result.setCollectorItemId(ba.getCollectorItemId());
		}
	}


	private long convertTimestamp(String sTimestamp){
		long timestamp = 0;
		if (sTimestamp != null) {
			try {
				Date date = FULL_DATE.parse(sTimestamp);
				timestamp = date.getTime();
			} catch (java.text.ParseException e) {
				LOGGER.error("Parsing artifact timestamp: " + sTimestamp, e);
			}
		}
		return timestamp;
	}
	@SuppressWarnings("PMD.AvoidDeeplyNestedIfStmts")
	private void addMetadataToArtifact(BinaryArtifact ba, JSONObject jsonArtifact) {
		if (ba != null && jsonArtifact != null) {
			JSONArray jsonProperties = getJsonArray(jsonArtifact, "properties");
			for (Object property : jsonProperties) {
				JSONObject jsonProperty = (JSONObject) property;
				String key = getString(jsonProperty, "key");
				String value = getString(jsonProperty, "value");
				switch (key) {
					case "build.url":
					case "build_url":
					case "buildUrl":
						ba.setBuildUrl(value);
						break;
					case "build.number":
					case "build_number":
					case "buildNumber":
						ba.setBuildNumber(value);
						break;
					case "job.url":
					case "job_url":
					case "jobUrl":
						ba.setJobUrl(value);
						break;
					case "job.name":
					case "job_name":
					case "jobName":
						ba.setJobName(value);
						break;
					case "instance.url":
					case "instance_url":
					case "instanceUrl":
						ba.setInstanceUrl(value);
						break;
					case "vcs.url":
					case "vcs_url":
					case "vcsUrl":
						ba.setScmUrl(value);
						break;
					case "vcs.branch":
					case "vcs_branch":
					case "vcsBranch":
						ba.setScmBranch(value);
						break;
					case "vcs.revision":
					case "vcs_revision":
					case "vcsRevision":
						ba.setScmRevisionNumber(value);
						break;
					default:
						// MongoDB doesn't allow dots in keys. So we handle it by converting
						// the letter following it to uppercase, and ignoring the dot.
						if (key.contains(".")) {
							StringBuilder newKey = new StringBuilder();
							char prevChar = 0;
							for (char c : key.toCharArray()) {
								if (c != '.') {
									if (prevChar == '.') {
										c = Character.toUpperCase(c);
									}
									newKey.append(c);
								}
								prevChar = c;
							}
							key = newKey.toString();
						}
						if (StringUtils.isNotEmpty(key)) {
							ba.getMetadata().put(key, value);
						}
						break;
				}
			}
		}
	}

	// Helpers

	private ResponseEntity makeRestCall(String instanceUrl, String suffix) {
		ResponseEntity response = null;
		String url = joinUrl(instanceUrl, artifactorySettings.getEndpoint(), suffix);
		try {
			HttpHeaders headers = createHeaders(instanceUrl);
			response = restClient.makeRestCallGet(url, headers);

		} catch (RestClientException re) {
			LOGGER.error("Error with REST url: " + url);
			LOGGER.error(re.getMessage());
		}
		return response;
	}

	private ResponseEntity makeRestPost(String instanceUrl, String suffix, MediaType contentType, String body) {
		ResponseEntity response = null;
		String url = joinUrl(instanceUrl, artifactorySettings.getEndpoint(), suffix);
		try {
			HttpHeaders headers = createHeaders(instanceUrl);
			headers.setContentType(contentType);
			headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
			response = restClient.makeRestCallPost(url, headers, body);
		} catch (HttpClientErrorException re) {
			LOGGER.error("Error with REST url: " + url);
			LOGGER.error(re.getMessage() + ": " + re.getResponseBodyAsString());
		}
		return response;
	}

	// join a base url to another path or paths - this will handle trailing or non-trailing /'s
	private String joinUrl(String url, String... paths) {
		StringBuilder result = new StringBuilder(url);
		for (String path : paths) {
			if (path != null) {
				String p = path.replaceFirst("^(\\/)+", "");
				if (result.lastIndexOf("/") != result.length() - 1) {
					result.append('/');
				}
				result.append(p);
			}
		}
		return result.toString();
	}

	protected HttpHeaders createHeaders(String instanceUrl) {
		HttpHeaders headers = new HttpHeaders();
		List servers = this.artifactorySettings.getServers();
		List userNames = new ArrayList<>();
		List apiKeys = new ArrayList<>();
		servers.forEach(serverSetting -> {
			userNames.add(serverSetting.getUsername());
			apiKeys.add(serverSetting.getApiKey());
		});

		if (CollectionUtils.isNotEmpty(servers) && CollectionUtils.isNotEmpty(userNames) && CollectionUtils.isNotEmpty(apiKeys)) {
			for (int i = 0; i < servers.size(); i++) {
				ServerSetting serverSetting = servers.get(i);
				if (serverSetting != null && serverSetting.getUrl().contains(instanceUrl)
						&& i < userNames.size() && i < apiKeys.size() && userNames.get(i) != null && apiKeys.get(i) != null) {
					String userInfo = userNames.get(i) + ":" + apiKeys.get(i);
					byte[] encodedAuth = Base64.encodeBase64(
							userInfo.getBytes(StandardCharsets.US_ASCII));
					String authHeader = "Basic " + new String(encodedAuth);
					headers.set(HttpHeaders.AUTHORIZATION, authHeader);
				}
			}
		}
		return headers;
	}

	private JSONArray getJsonArray(JSONObject json, String key) {
		Object array = json.get(key);
		return array == null ? new JSONArray() : (JSONArray) array;
	}

	private String getString(JSONObject json, String key) {
		return (String) json.get(key);
	}

	private ArtifactItem createArtifactItem(String instanceUrl, String repo, String artName, String artPath) {
		ArtifactItem artifactItem = new ArtifactItem();
		artifactItem.setInstanceUrl(instanceUrl);
		artifactItem.setRepoName(repo);
		artifactItem.setArtifactName(artName);
		artifactItem.setPath(artPath);
		artifactItem.setDescription(artName);
		artifactItem.setLastUpdated(System.currentTimeMillis());
		return artifactItem;
	}



	private String removeLeadAndTrailingSlash(String path){
		path = removeSlash(path, "/+$");
		path = removeSlash(path, "^/+");
		return path;
	}

	private String removeSlash(String path, String s) {
		if(StringUtils.isNotEmpty(path)){
			return path.replaceAll(s, "");
		}
		return "";
	}

	private String truncate(String name){
		name = removeLeadAndTrailingSlash(name);
		if(name.indexOf(SLASH) > 0){
			return name.substring(0, name.indexOf(SLASH));
		}
		return name;
	}

	private String normalizePath(String path, String repoName){
		path = removeLeadAndTrailingSlash(path);
		if(path.indexOf(SLASH) > 0) return path;
		return repoName+ SLASH +path;
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy