package org.zalando.catwatch.backend.service; import org.springframework.data.domain.PageRequest; import org.zalando.catwatch.backend.model.Statistics; import org.zalando.catwatch.backend.repo.StatisticsRepository; import org.zalando.catwatch.backend.util.Constants; import org.zalando.catwatch.backend.util.StringParser; import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; public class StatisticsService { public static Collection<Statistics> getStatistics(StatisticsRepository repository, Collection<String> organizations, String startDate, String endDate){ Collection<Statistics> statistics = new ArrayList<>(organizations.size()); List<Statistics> unaggregatedStatistics = new ArrayList<>(); if (startDate == null && endDate == null) { for (String orgName : organizations) { List<Statistics> s = repository.findByOrganizationNameOrderByKeySnapshotDateDesc(orgName, new PageRequest(0, 1)); unaggregatedStatistics.addAll(s); } if (unaggregatedStatistics.size() > 0) { Statistics aggregatedStatistics = aggregateStatistics(unaggregatedStatistics); statistics.add(aggregatedStatistics); } } else { // filter by start and end date statistics = getStatisticsByDate(repository, organizations, startDate, endDate); } return statistics; } private static Collection<Statistics> getStatisticsByDate(StatisticsRepository repository, Collection<String> orgs, String startDate, String endDate) { Date start = null; Date end; try { if (startDate != null) { start = StringParser.parseIso8601Date(startDate); } } catch (ParseException e) { throw new IllegalArgumentException(Constants.ERR_MSG_WRONG_DATE_FORMAT + " for stardDate"); } try { end = endDate == null ? new Date() : StringParser.parseIso8601Date(endDate); } catch (ParseException e) { throw new IllegalArgumentException(Constants.ERR_MSG_WRONG_DATE_FORMAT + " for endDate"); } List<List<Statistics>> statisticsLists = collectStatistics(repository, orgs, start, end); return aggregateHistoricalStatistics(statisticsLists); } private static List<List<Statistics>> collectStatistics(StatisticsRepository repository, Collection<String> organizations, Date start, Date end) { List<List<Statistics>> statisticsLists = new ArrayList<>(); // get statistics for each organization for (String orgName : organizations) { if (start == null) { Optional<Date> earliestSnapshot = repository.getEarliestSnaphotDate(orgName); if (earliestSnapshot.isPresent()) { start = earliestSnapshot.get(); } else { continue; } } else { Optional<Date> earlierSnapshot = repository.getLatestSnaphotDateBefore(orgName, start); if (earlierSnapshot.isPresent()) { start = earlierSnapshot.get(); } } if (start.after(end)) { continue; // throw new IllegalArgumentException("Start date is after end // date"); } List<Statistics> s = repository.findStatisticsByOrganizationAndDate(orgName, start, end); statisticsLists.add(s); } return statisticsLists; } /** * Aggregates a collection of {@link Statistics} objects by adding up their field values * * @param statistics The {@link Collection} of {@link Statistics} objects to be merged * @return A {@link Statistics} object whole field values are the sum of the given field values of the input Statistics objects. * @throws IllegalArgumentException If an invariant has been violated */ public static Statistics aggregateStatistics(Collection<Statistics> statistics) throws IllegalArgumentException{ if(statistics==null || statistics.size()==0){ throw new IllegalArgumentException("Illegal number of statistics to aggregate"); } String delimeter = ", "; if(statistics.size()==1) return statistics.iterator().next(); Integer contributers = 0, externalContributors = 0, forks = 0, size = 0, stars = 0, members = 0, privateProjects = 0, languages = 0, publicProjects = 0, tags = 0, teams = 0; Date snapshotDate = null; Set<String> organizationList = new HashSet<>(); //aggregate data for (Statistics s : statistics) { contributers = add(contributers, s.getAllContributorsCount()); externalContributors = add(externalContributors, s.getExternalContributorsCount()); forks = add(forks, s.getAllForksCount()); size = add(size, s.getAllSizeCount()); stars = add(stars, s.getAllStarsCount()); members = add(members, s.getMembersCount()); privateProjects = add(privateProjects, s.getPrivateProjectCount()); languages = add(languages, s.getProgramLanguagesCount()); publicProjects = add(publicProjects, s.getPublicProjectCount()); tags = add(tags, s.getTagsCount()); teams = add(teams, s.getTeamsCount()); organizationList.add(s.getOrganizationName()); if(snapshotDate==null) snapshotDate = s.getSnapshotDate(); else if(snapshotDate.before(s.getSnapshotDate())) snapshotDate = s.getSnapshotDate(); } String organizations = null; for(String org : organizationList){ if (organizations == null) organizations = org; else organizations += delimeter + org; } //save aggregated values in new statistics object Statistics s = new Statistics(new Double(Math.random()*10000).intValue() , snapshotDate); s.setAllContributorsCount(contributers); s.setExternalContributorsCount(externalContributors); s.setAllForksCount(forks); s.setAllSizeCount(size); s.setAllStarsCount(stars); s.setMembersCount(members); s.setOrganizationName(organizations); s.setPrivateProjectCount(privateProjects); s.setProgramLanguagesCount(languages); s.setPublicProjectCount(publicProjects); s.setTagsCount(tags); s.setTeamsCount(teams); return s; } public static Collection<Statistics> aggregateHistoricalStatistics(List<List<Statistics>> statisticsLists){ if(statisticsLists.isEmpty()) return Collections.emptyList(); if(statisticsLists.size()==1){ return statisticsLists.iterator().next(); } //TODO check if the lists have the same size //map statistics of the different organizations List<Statistics> aggregatedStatistics = new ArrayList<>(); int numberOfRecords = statisticsLists.get(0).size(); //assuming that all organizations have the same amount of records List<Statistics> unaggregatedStatistics; for (int i=0; i<numberOfRecords; i++){ unaggregatedStatistics = new ArrayList<>(); for (List<Statistics> orgStats : statisticsLists){ if(orgStats.isEmpty()) continue; //FIXME figure out how to map the records //for now just use the order unaggregatedStatistics.add(orgStats.get(i)); } Statistics aggregatedRecord = aggregateStatistics(unaggregatedStatistics); aggregatedStatistics.add(aggregatedRecord); } return aggregatedStatistics; } private static Integer add(Integer sum, Integer value){ int tempSum = sum == null ? 0 : sum; if(value!=null) tempSum += value; return tempSum; } }