package org.nextprot.api.web.service.impl; import java.io.IOException; import java.net.URLEncoder; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.kohsuke.github.GHContent; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GHTree; import org.kohsuke.github.GHTree.GHTreeEntry; import org.kohsuke.github.GitHub; import org.nextprot.api.commons.exception.NextProtException; import org.nextprot.api.commons.utils.StringUtils; import org.nextprot.api.web.domain.NextProtNews; import org.nextprot.api.web.service.GitHubService; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; /** * * Service that communicates with github to retrieve the pages * * @author dteixeira * */ @Service public class GitHubServiceImpl implements GitHubService { private static final Log LOGGER = LogFactory.getLog(GitHubServiceImpl.class); private String githubToken = null; private String githubDocBranch = null; private Map<String, String> newsFileNames = new HashMap<>(); // will refresh every minute, because anonymous calls are limited to 60 // calls per hour /** * Connects anonymously if token is not defined * @return * @throws IOException */ private GitHub getGitHubConnection () throws IOException{ if((githubToken == null) || githubToken.equalsIgnoreCase("undefined")){ return GitHub.connectAnonymously(); }else return GitHub.connectUsingOAuth(githubToken); } @Override @Cacheable(value = "github-pages") public String getPage(String folder, String page) { String finalPage = page; if("news".equalsIgnoreCase(folder)){ finalPage = getCorrespondingPageForNews(page); } try { GitHub github = getGitHubConnection(); GHRepository repo = github.getRepository("calipho-sib/nextprot-docs"); GHContent content = null; String extension = ".md"; if(folder.contains("json")){ //if folder contains json extension = ".json"; } content = repo.getFileContent(folder + "/" + finalPage + extension, githubDocBranch); return content.getContent(); } catch (IOException e) { e.printStackTrace(); throw new NextProtException("Documentation not available, sorry for the inconvenience"); } } private String getCorrespondingPageForNews(String url) { String correspondingPage = url; // I don't like very much this code... // discuss how to do this better if (newsFileNames.isEmpty()) { getNews(); } if (newsFileNames.containsKey(url)) { // corresponds to URL correspondingPage = newsFileNames.get(url); // some processing for // the final page in // case of news } else { getNews(); // will reload newsFileNames if (newsFileNames.containsKey(url)) { // try a second time correspondingPage = newsFileNames.get(url); } else { throw new NextProtException(url + " not found"); } } return correspondingPage; } @Override @Cacheable(value = "github-tree") public GHTree getTree() { try { GitHub github = getGitHubConnection(); GHRepository repo = github.getRepository("calipho-sib/nextprot-docs"); return repo.getTreeRecursive(githubDocBranch, 1); } catch (IOException e) { e.printStackTrace(); throw new NextProtException("Documentation not available, sorry for the inconvenience"); } } @Override @Cacheable(value = "github-news") public List<NextProtNews> getNews() { List<NextProtNews> news = new ArrayList<>(); try { GitHub github = getGitHubConnection(); GHRepository repo = github.getRepository("calipho-sib/nextprot-docs"); GHTree tree = repo.getTreeRecursive(githubDocBranch, 1); newsFileNames.clear(); for(GHTreeEntry te : tree.getTree()){ if(te.getPath().startsWith("news")){ //Add only file on news if(te.getType().equalsIgnoreCase("blob")){ // file String fileName = te.getPath().replaceAll("news/", ""); NextProtNews n = parseGitHubNewsFilePath(fileName); if(n != null){ news.add(n); String fileEncoded = URLEncoder.encode(fileName.replace(".md", ""), "UTF-8").replace("+", "%20"); newsFileNames.put(n.getUrl(), fileEncoded); } } } } } catch (IOException e) { e.printStackTrace(); throw new NextProtException("News not available, sorry for the inconvenience"); } Collections.sort(news); return news; } @Value("${github.accesstoken}") public void setGithubToken(String githubToken) { this.githubToken = githubToken; } @Value("${github.doc.branch}") public void setGithubDocBranch(String githubDocBranch) { this.githubDocBranch = githubDocBranch; } static NextProtNews parseGitHubNewsFilePath(String filePath){ NextProtNews result = new NextProtNews(); if(filePath == null || filePath.equals("")){ return null; } String[] elements = filePath.split("\\/"); if(elements.length != 4){ LOGGER.warn("Number of elements is different than 4 != " + elements.length + " " + elements); return null; } //Check for elements not null or empty for(String elem : elements){ if(elem == null || elem.isEmpty()){ LOGGER.warn("Found a null or empty element on " + filePath); return null; } } try { // Parse the date DateFormat df = new SimpleDateFormat("yyyy/MM/dd"); Date date = df.parse(elements[0] + "/" + elements[1] + "/" + elements[2]); result.setPublicationDate(date); } catch (ParseException e) { LOGGER.warn("Failed to parse the date for file" + filePath + " " + e.getMessage()); //e.printStackTrace(); return null; } //Gets url and title String title = elements[3].replace(".md", "").trim(); result.setTitle(title); result.setUrl(normalizeTitleToUrl(title)); return result; } public static String normalizeTitleToUrl(String title){ String charClass = "!?:;,/(){}\\\\"; String url = StringUtils.slug(title, "[" + charClass + "]", "-").toLowerCase(); String url1 = url.replaceAll("[" + charClass + "-]+$", ""); String url2 = url1.replaceAll("^[" + charClass + "-]+", ""); return url2.replaceAll("-{2,}", "-"); // replaces } }