package de.is24.infrastructure.gridfs.http.repos;
import com.google.common.collect.Lists;
import com.mongodb.AggregationOutput;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import de.is24.infrastructure.gridfs.http.domain.Container;
import de.is24.infrastructure.gridfs.http.domain.FileInfo;
import de.is24.infrastructure.gridfs.http.domain.FolderInfo;
import de.is24.infrastructure.gridfs.http.domain.RepoEntry;
import de.is24.infrastructure.gridfs.http.domain.RepoType;
import de.is24.infrastructure.gridfs.http.domain.SortField;
import de.is24.infrastructure.gridfs.http.domain.SortOrder;
import de.is24.infrastructure.gridfs.http.exception.RepositoryNotFoundException;
import de.is24.infrastructure.gridfs.http.metadata.RepoEntriesRepository;
import de.is24.infrastructure.gridfs.http.mongo.DatabaseStructure;
import de.is24.util.monitoring.spring.TimeMeasurement;
import org.apache.commons.collections.ComparatorUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import static com.google.common.base.Strings.isNullOrEmpty;
import static de.is24.infrastructure.gridfs.http.domain.FolderInfo.fromRepoEntry;
import static de.is24.infrastructure.gridfs.http.domain.RepoType.SCHEDULED;
import static de.is24.infrastructure.gridfs.http.domain.RepoType.STATIC;
import static de.is24.infrastructure.gridfs.http.domain.SortField.name;
import static de.is24.infrastructure.gridfs.http.domain.SortOrder.asc;
import static de.is24.infrastructure.gridfs.http.mongo.DatabaseStructure.FILENAME_KEY;
import static de.is24.infrastructure.gridfs.http.mongo.DatabaseStructure.GRIDFS_FILES_COLLECTION;
import static de.is24.infrastructure.gridfs.http.mongo.DatabaseStructure.METADATA_ARCH_KEY;
import static de.is24.infrastructure.gridfs.http.mongo.DatabaseStructure.METADATA_REPO_KEY;
import static de.is24.infrastructure.gridfs.http.mongo.DatabaseStructure.REPO_KEY;
import static de.is24.infrastructure.gridfs.http.mongo.MongoAggregationBuilder.groupBy;
import static de.is24.infrastructure.gridfs.http.mongo.MongoAggregationBuilder.match;
import static de.is24.infrastructure.gridfs.http.mongo.MongoAggregationBuilder.sort;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;
import static org.springframework.data.mongodb.gridfs.GridFsCriteria.whereMetaData;
@Service
public class StaticRepositoryInfoProvider implements RepositoryInfoProvider {
private static final Pattern PATTERN = Pattern.compile("/+$");
private final MongoTemplate mongoTemplate;
private final RepoEntriesRepository entriesRepository;
private static final Set<RepoType> STATIC_TYPES = new HashSet<>(Arrays.asList(STATIC, SCHEDULED));
@Autowired
public StaticRepositoryInfoProvider(MongoTemplate mongoTemplate, RepoEntriesRepository entriesRepository) {
this.mongoTemplate = mongoTemplate;
this.entriesRepository = entriesRepository;
}
@Override
@TimeMeasurement
public Container<FolderInfo> getRepos(SortField sortBy, SortOrder sortOrder) {
final Iterable<DBObject> reposAggregation = getReposAggregation(sortBy, sortOrder);
List<FolderInfo> folderInfos = adaptFolders(reposAggregation);
return new Container<>("", addEmptyRepositories(folderInfos, sortBy, sortOrder));
}
private List<FolderInfo> addEmptyRepositories(List<FolderInfo> folderInfos, SortField sortBy, SortOrder sortOrder) {
final List<RepoEntry> staticRepos = entriesRepository.findByTypeIn(SCHEDULED, STATIC);
for (RepoEntry staticRepo : staticRepos) {
FolderInfo folderInfo = fromRepoEntry(staticRepo, 0);
if (!folderInfos.contains(folderInfo)) {
folderInfos.add(folderInfo);
}
}
return sortFolderInfos(folderInfos, sortBy, sortOrder);
}
private List<FolderInfo> sortFolderInfos(List<FolderInfo> folderInfos, SortField sortBy, SortOrder sortOrder) {
List<FolderInfo> list = new ArrayList<>(folderInfos);
Comparator<FolderInfo> comparator = getComparator(sortBy);
if (sortOrder == asc) {
Collections.sort(list, comparator);
} else {
Collections.sort(list, getReversedComparator(comparator));
}
return list;
}
@SuppressWarnings("unchecked")
private Comparator<FolderInfo> getReversedComparator(final Comparator<FolderInfo> comparator) {
return ComparatorUtils.reversedComparator(comparator);
}
private Comparator<FolderInfo> getComparator(SortField sortBy) {
switch (sortBy) {
case uploadDate: {
return (folder1, folder2) -> folder1.getLastModified().compareTo(folder2.getLastModified());
}
case size: {
return (folder1, folder2) -> Long.compare(folder1.getSize(), folder2.getSize());
}
default: {
return (folder1, folder2) -> folder1.getName().compareTo(folder2.getName());
}
}
}
@Override
public Iterable<DBObject> getReposAggregation(SortField sortBy, SortOrder sortOrder) {
List<DBObject> pipeline = new ArrayList<>();
pipeline.add(match(where(METADATA_REPO_KEY).ne(null)));
pipeline.add(groupBy(METADATA_REPO_KEY).sum("length").max("uploadDate").build());
pipeline.add(sort(sortBy, sortOrder));
AggregationOutput aggregation = getFilesCollection().aggregate(pipeline);
return aggregation.results();
}
@Override
@TimeMeasurement
public Container<FolderInfo> getArchs(String reponame, SortField sortBy, SortOrder sortOrder) {
verifyRepositoryExists(reponame);
List<DBObject> pipeline = new ArrayList<>();
pipeline.add(match(whereMetaData(REPO_KEY).is(reponame)));
pipeline.add(groupBy(METADATA_ARCH_KEY).sum("length").max("uploadDate").build());
pipeline.add(sort(sortBy, sortOrder));
AggregationOutput aggregation = getFilesCollection().aggregate(pipeline);
return new Container<>(reponame, adaptFolders(aggregation.results()));
}
private void verifyRepositoryExists(final String reponame) {
if (null == entriesRepository.findFirstByName(reponame)) {
throw new RepositoryNotFoundException("Repository not found.", reponame);
}
}
@Override
@TimeMeasurement
public Container<FileInfo> getFileInfo(String reponame, String arch, SortField sortBy, SortOrder sortOrder) {
verifyRepositoryExists(reponame);
DBCursor cursor = getFilesCollection().find(query(
whereMetaData(REPO_KEY) //
.is(reponame).andOperator(whereMetaData(DatabaseStructure.ARCH_KEY).is(arch))).getQueryObject())
.sort(sortBy.sortFile(sortOrder));
return new Container<>(reponame + "/" + arch, adaptFiles(cursor.iterator()));
}
@Override
@TimeMeasurement
public Container<FileInfo> find(String filenameRegex, SortField sortBy,
SortOrder sortOrder) {
return find(filenameRegex, "", "", sortBy, sortOrder);
}
@Override
@TimeMeasurement
public Container<FileInfo> find(String filenameRegex, String repo, SortField sortBy, SortOrder sortOrder) {
return find(filenameRegex, repo, "", sortBy, sortOrder);
}
@Override
@TimeMeasurement
public Container<FileInfo> find(String filenameRegex, String repo, String arch, SortField sortBy,
SortOrder sortOrder) {
DBCursor cursor = getFilesCollection().find(
query(
where(FILENAME_KEY) //
.regex(filenameRegex).and(METADATA_REPO_KEY).regex(addLineEndings(repo)) //
.and(METADATA_ARCH_KEY).regex(addLineEndings(arch))) //
.getQueryObject())
.sort(sortBy.sortFile(sortOrder));
return new Container<>(removeTrailingSlashes(repo + "/" + arch), eventuallySortByFilename(adaptFiles(cursor), sortBy, sortOrder));
}
private List<FileInfo> eventuallySortByFilename(List<FileInfo> fileInfos, SortField sortBy, SortOrder sortOrder) {
if (name.equals(sortBy)) {
final int direction = asc.equals(sortOrder) ? 1 : -1;
Collections.sort(fileInfos, (f1, f2) -> f1.getFilename().compareTo(f2.getFilename()) * direction);
}
return fileInfos;
}
@Override
public RepoType[] getValidRepoTypes() {
return new RepoType[] { STATIC, SCHEDULED };
}
private String removeTrailingSlashes(String s) {
return PATTERN.matcher(s).replaceAll("");
}
private DBCollection getFilesCollection() {
return mongoTemplate.getCollection(GRIDFS_FILES_COLLECTION);
}
private String addLineEndings(String s) {
return isNullOrEmpty(s) ? ".*" : ('^' + s + '$');
}
private List<FolderInfo> adaptFolders(Iterable<DBObject> dbObjects) {
List<FolderInfo> result = Lists.newArrayList();
Map<String, RepoEntry> repoEntries = getRepoEntriesByRepoName();
for (DBObject object : dbObjects) {
FolderInfo folderInfo = new FolderInfo(object);
RepoEntry repoEntry = repoEntries.get(folderInfo.getName());
if (repoEntry != null) {
folderInfo.setTags(repoEntry.getTags());
} else {
folderInfo.setTags(Collections.<String>emptySet());
}
result.add(folderInfo);
}
return result;
}
private Map<String, RepoEntry> getRepoEntriesByRepoName() {
return entriesRepository.findByTypeIn(STATIC, SCHEDULED).stream().collect(toMap(RepoEntry::getName, identity()));
}
@Override
@TimeMeasurement
public List<RepoEntry> find(String repoNameRegex, String tag, Date newer, Date older) {
return entriesRepository //
.findByTypeInAndNameMatchesRegexAndTagsContainsAndLastModifiedIsBetween(STATIC_TYPES, repoNameRegex, tag, newer,
older);
}
@Override
@TimeMeasurement
public List<RepoEntry> find(String repoNameRegex, Date newer, Date older) {
return entriesRepository //
.findByTypeInAndNameMatchesRegexAndLastModifiedIsBetween(STATIC_TYPES, repoNameRegex, newer, older);
}
private List<FileInfo> adaptFiles(Iterator<DBObject> itr) {
List<FileInfo> fileInfos = new ArrayList<>();
while (itr.hasNext()) {
fileInfos.add(new FileInfo(itr.next()));
}
return fileInfos;
}
@Override
public List<RepoEntry> find(String repoNameRegex) {
return null;
}
@Override
public boolean isExternalRepo(String repoName) {
return false;
}
@Override
public String getRedirectUrl(String repoName) {
throw new IllegalArgumentException(repoName + "is not an external repo");
}
@Override
public String getRedirectUrl(String repoName, String arch) {
throw new IllegalArgumentException(repoName + "is not an external repo");
}
}