package sagan.guides.support; import sagan.guides.AbstractGuide; import sagan.guides.ContentProvider; import sagan.guides.DefaultGuideMetadata; import sagan.guides.GuideMetadata; import sagan.guides.ImageProvider; import sagan.projects.support.ProjectMetadataService; import sagan.support.ResourceNotFoundException; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestClientException; /** * Repository implementation providing data access services for guides by repo prefix. */ public abstract class PrefixDocRepository<T extends AbstractGuide> implements DocRepository<T, GuideMetadata>, ContentProvider<T>, ImageProvider { protected final Logger log = LoggerFactory.getLogger(getClass()); private static final String REPO_BASE_PATH = "/repos/%s/%s"; private static final String README_PATH_ASC = REPO_BASE_PATH + "/zipball"; private final MultiValueMap<String, String> tagMultimap = new LinkedMultiValueMap<>(); private final GuideOrganization org; private final ProjectMetadataService projectMetadataService; private final AsciidoctorUtils asciidoctorUtils = new AsciidoctorUtils(); private String prefix; public PrefixDocRepository(GuideOrganization org, ProjectMetadataService projectMetadataService, String prefix) { this.org = org; this.projectMetadataService = projectMetadataService; this.prefix = prefix; } @Override public T find(String tutorial) { return populate(create(findMetadata(tutorial))); } public void evictFromCache(String guide) { log.info("Tutorial evicted from cache: {}", guide); } protected abstract T create(GuideMetadata findMetadata); public GuideMetadata findMetadata(String tutorial) { String repoName = this.prefix + tutorial; String description = getRepoDescription(repoName); Set<String> tags = tagMultimap.get(repoName) != null ? new HashSet<>(tagMultimap.get(repoName)) : Collections.emptySet(); return new DefaultGuideMetadata(org.getName(), tutorial, repoName, description, tags); } @Override public List<GuideMetadata> findAllMetadata() { return org.findRepositoriesByPrefix(this.prefix) .stream() .map(repo -> new DefaultGuideMetadata(org.getName(), repo.getName().replaceAll("^" + this.prefix, ""), repo.getName(), repo.getDescription(), new HashSet<String>(tagMultimap.getOrDefault(repo .getName(), Collections.emptyList())))) .collect(Collectors.toList()); } @Override public List<T> findAll() { return findAllMetadata() .stream() .map(this::create) .map(this::populate) .collect(Collectors.toList()); } @Override public T populate(T tutorial) { String repoName = tutorial.getRepoName(); AsciidocGuide asciidocGuide = asciidoctorUtils.getDocument(org, String.format(README_PATH_ASC, org.getName(), repoName)); tagMultimap.merge(repoName, new ArrayList<>(asciidocGuide.getTags()), (source, target) -> { Set<String> tags = new LinkedHashSet<>(target); tags.addAll(source); return new ArrayList<>(tags); }); tutorial.setContent(asciidocGuide.getContent()); tutorial.setSidebar(asciidoctorUtils.generateDynamicSidebar(projectMetadataService, asciidocGuide)); return tutorial; } @Override public byte[] loadImage(GuideMetadata tutorialMetadata, String imageName) { try { return org.getGuideImage(tutorialMetadata.getRepoName(), imageName); } catch (RestClientException ex) { String msg = String.format("Could not load image '%s' for repo '%s'", imageName, tutorialMetadata .getRepoName()); log.warn(msg, ex); throw new ResourceNotFoundException(msg, ex); } } public String parseGuideName(String repositoryName) { Assert.hasText(repositoryName); Assert.isTrue(repositoryName.startsWith(this.prefix)); return repositoryName.substring(this.prefix.length()); } protected String getRepoDescription(String repoName) { String description; try { description = org.getRepoInfo(repoName).getDescription(); } catch (RestClientException ex) { description = ""; } return description; } }