Java tutorial
/* * See the NOTICE file distributed with this work for additional * information regarding copyright ownership. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.xwiki.contrib.githubstats.internal; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; import org.gitective.core.stat.UserCommitActivity; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GHUser; import org.kohsuke.github.GitHub; import org.kohsuke.github.PagedSearchIterable; import org.slf4j.Logger; import org.xwiki.component.annotation.Component; import org.xwiki.context.Execution; import org.xwiki.contrib.githubstats.Author; import org.xwiki.contrib.githubstats.GitHubStatsManager; import org.xwiki.contrib.githubstats.GitHubRepository; import org.xwiki.contrib.githubstats.GitHubStatsException; import org.xwiki.git.GitManager; import org.xwiki.model.EntityType; import org.xwiki.model.reference.EntityReference; import org.xwiki.model.reference.EntityReferenceSerializer; import org.xwiki.query.Query; import org.xwiki.query.QueryException; import org.xwiki.query.QueryManager; import com.xpn.xwiki.XWikiContext; import com.xpn.xwiki.XWikiException; import com.xpn.xwiki.doc.XWikiDocument; import com.xpn.xwiki.objects.BaseObject; @Component @Singleton public class DefaultGitHubStatsManager implements GitHubStatsManager { /** * Space where the GitHubStats application pages are located. */ String SPACE = "GitHubStats"; /** * GitHubStats.WebHome. */ EntityReference PARENT = new EntityReference("WebHome", EntityType.DOCUMENT, new EntityReference(SPACE, EntityType.SPACE)); /** * GitHubStats.AuthorClass xclass. */ EntityReference AUTHOR_CLASS = new EntityReference("AuthorClass", EntityType.DOCUMENT, new EntityReference(SPACE, EntityType.SPACE)); /** * GitHubStats.AuthorRepositoryClass xclass. */ EntityReference AUTHOR_REPOSITORY_CLASS = new EntityReference("AuthorRepositoryClass", EntityType.DOCUMENT, new EntityReference(SPACE, EntityType.SPACE)); /** * GitHubStats.RepositoryClass xclass. */ EntityReference REPOSITORY_CLASS = new EntityReference("RepositoryClass", EntityType.DOCUMENT, new EntityReference(SPACE, EntityType.SPACE)); @Inject private Logger logger; @Inject private GitManager gitManager; @Inject private QueryManager queryManager; @Inject private EntityReferenceSerializer<String> defaultSerializer; @Inject private Execution execution; @Inject @Named("compactwiki") private EntityReferenceSerializer<String> compactWikiSerializer; @Override public Map<Author, Set<GitHubRepository>> findAllAuthors() throws GitHubStatsException { // For each repository found, find all authors that have ever contributed code. Map<Author, Set<GitHubRepository>> authors = new HashMap<>(); for (Map.Entry<GitHubRepository, String> entry : getAllRepositoryURLs().entrySet()) { try { Repository repository = getRepository(entry.getValue(), entry.getKey()); for (PersonIdent personIdent : this.gitManager.findAuthors(Arrays.asList(repository))) { Author author = new Author(personIdent.getName(), personIdent.getEmailAddress()); Set<GitHubRepository> reposForAuthor = authors.get(author); if (reposForAuthor == null) { reposForAuthor = new HashSet<>(); authors.put(author, reposForAuthor); } reposForAuthor.add(new GitHubRepository(entry.getKey().getOrganizationId(), entry.getKey().getRepositoryId())); } } catch (Exception e) { // A repository can fail to be cloned/updated (for example if it's empty), in which case we simply // ignore it. } } return authors; } @Override public List<String> importAuthor(String authorId, String authorEmail, Collection<GitHubRepository> repositories, boolean overwrite) throws GitHubStatsException { List<String> importedUsers = new ArrayList<>(); BaseObject authorObject = importAuthorInternal(authorId, authorEmail, repositories, overwrite); if (authorObject != null) { String authorAsString = String.format("%s <%s>", authorId, authorEmail); importedUsers.add(authorAsString); } return importedUsers; } private BaseObject importAuthorInternal(String authorId, String authorEmail, Collection<GitHubRepository> repositories, boolean overwrite) throws GitHubStatsException { BaseObject authorObject = null; String authorAsString = String.format("%s <%s>", authorId, authorEmail); // Create a page based on the name + email (for unicity) and fill it with basic data try { // Get author document or create a new one if it doesn't exist XWikiDocument authorDocument = getAuthorDocument(authorId, authorEmail); if (authorDocument.isNew() || overwrite) { authorDocument.setTitle(String.format("Author [%s]", authorAsString)); authorDocument.setParentReference(PARENT); authorDocument.setHidden(true); // If there's an existing AuthorClass xobject then reuse it, otherwise create it authorObject = createAuthorClass(authorDocument); authorObject.setStringValue("id", authorId); authorObject.setStringValue("email", authorEmail); // Merge AuthorRepositoryClass xobjects by removing all existing objects and adding new ones authorDocument.removeXObjects(AUTHOR_REPOSITORY_CLASS); XWikiContext xcontext = getXWikiContext(); addAuthorRepositoryObjects(authorDocument, repositories, xcontext); // Save modifications xcontext.getWiki().saveDocument(authorDocument, "Imported author from Git", true, xcontext); } } catch (XWikiException e) { throw new GitHubStatsException( String.format("Failed to create or update author document for [%s]", authorAsString), e); } return authorObject; } @Override public List<String> importAllAuthors(boolean overwrite) throws GitHubStatsException { List<String> importedUsers = new ArrayList<>(); Map<Author, Set<GitHubRepository>> authors = findAllAuthors(); for (Map.Entry<Author, Set<GitHubRepository>> entry : authors.entrySet()) { Author author = entry.getKey(); Set<GitHubRepository> repositories = entry.getValue(); importedUsers.addAll(importAuthor(author.getId(), author.getEmail(), repositories, overwrite)); } return importedUsers; } @Override public List<String> importAllAuthorsFromGitHub(GitHub gitHub, boolean overwrite) throws GitHubStatsException { List<String> updatedUsers = new ArrayList<>(); // Find all authors already imported so that for each of them we look for more data on GitHub. try { List<BaseObject> authorObjects = getAuthorObjectsForQuery( String.format(", doc.object(%s) as author", this.defaultSerializer.serialize(AUTHOR_CLASS))); for (BaseObject authorObject : authorObjects) { try { // Only import if there are fields not set or if overwrite is true. This is to improve performances // since we need to call GitHub for each author existing in XWiki. String avatar = authorObject.getStringValue("avatar"); String name = authorObject.getStringValue("name"); if (overwrite || StringUtils.isEmpty(avatar) || StringUtils.isEmpty(name)) { String userId = authorObject.getStringValue("id"); String emailAddress = authorObject.getStringValue("email"); updatedUsers.addAll(importAuthorFromGitHub(gitHub, userId, emailAddress, overwrite)); } } catch (GitHubStatsException e) { // Failed to import the user. This is usually because the user doesn't exist but the GitHub API // we're using doesn't let us make the difference between a non existent users and a failure to // retrieve the user's data... // Thus we simply skip this user and continue... } } } catch (Exception e) { throw new GitHubStatsException("Failed to import all authors data from GitHub", e); } return updatedUsers; } private List<String> importAuthorFromGitHub(GHUser user, List<BaseObject> authorToUpdateObjects, boolean overwrite) throws GitHubStatsException { List<String> importedUsers = new ArrayList<>(); try { XWikiContext xcontext = getXWikiContext(); for (BaseObject authorToUpdateObject : authorToUpdateObjects) { boolean modified = false; String currentName = authorToUpdateObject.getStringValue("name"); if (StringUtils.isEmpty(currentName) || overwrite) { authorToUpdateObject.setStringValue("name", user.getName()); modified = true; } String currentAvatar = authorToUpdateObject.getStringValue("avatar"); if (StringUtils.isEmpty(currentAvatar) || overwrite) { authorToUpdateObject.setStringValue("avatar", user.getAvatarUrl()); modified = true; } String currentProfileURL = authorToUpdateObject.getStringValue("profileurl"); if (StringUtils.isEmpty(currentProfileURL) || overwrite) { // TODO: There's currently no way to get the User HTML URL, // See https://github.com/kohsuke/github-api/issues/52 // matchingAuthorObject.setStringValue("profileurl", user.get...); // modified = true; } String currentCompany = authorToUpdateObject.getStringValue("company"); if (StringUtils.isEmpty(currentCompany) || overwrite) { authorToUpdateObject.setStringValue("company", user.getCompany()); modified = true; } // Save modifications if any if (modified) { xcontext.getWiki().saveDocument(authorToUpdateObject.getOwnerDocument(), "Imported user data from GitHub", true, xcontext); importedUsers.add(authorToUpdateObject.getOwnerDocument().getDocumentReference().getName()); } } } catch (Exception e) { throw new GitHubStatsException("Failed to import author data from GitHub", e); } return importedUsers; } private List<String> importAuthorFromGitHub(GitHub gitHub, String authorId, String emailAddress, List<BaseObject> authorToUpdateObjects, boolean overwrite) throws GitHubStatsException { List<String> result = Collections.emptyList(); // Load the XWiki page corresponding to that user and fill the data. try { GHUser matchinguser = locateUserInGitHub(gitHub, authorId, emailAddress); if (matchinguser != null) { result = importAuthorFromGitHub(matchinguser, authorToUpdateObjects, overwrite); } } catch (Exception e) { throw new GitHubStatsException("Failed to import author data from GitHub", e); } return result; } private GHUser locateUserInGitHub(GitHub gitHub, String authorId, String emailAddress) { try { // Search for a user with the passed login // Note: We don't use "gitHub.getUser(authorId)" because if the authorId is a simple one (like "Gabriela") // then it's very likely that it'll return the wrong user. Doing a search is likely to return more than one // user and thus we'll search with the email address and full name. PagedSearchIterable<GHUser> matchingUsers = gitHub.searchUsers().q(escapeQueryTerm(authorId)) .type("user").in("login").list(); if (matchingUsers.getTotalCount() == 1) { return matchingUsers.iterator().next(); } // Search for a user with a matching email address matchingUsers = gitHub.searchUsers().q(escapeQueryTerm(emailAddress)).type("user").in("email").list(); if (matchingUsers.getTotalCount() == 1) { return matchingUsers.iterator().next(); } // Search for a user with a full name matching the passed login (since on git, sometimes users set their // name as their git id). matchingUsers = gitHub.searchUsers().q(escapeQueryTerm(authorId)).type("user").in("fullname").list(); if (matchingUsers.getTotalCount() == 1) { return matchingUsers.iterator().next(); } } catch (Throwable e) { // It failed to locate the user. The most likely reason is that the API rate limit has been reached. // Continue so that the users for which it has worked can be saved and so that the user can reimport the // rest later on. this.logger.warn("Failed to locate user [{}] (email [{}]). Reason: [{}]", authorId, emailAddress, ExceptionUtils.getRootCauseMessage(e)); } return null; } private String escapeQueryTerm(String term) { return StringUtils.prependIfMissing(StringUtils.appendIfMissing(term, "\""), "\""); } @Override public List<String> importAuthorFromGitHub(GitHub gitHub, String authorId, String emailAddress, boolean overwrite) throws GitHubStatsException { List<BaseObject> matchingAuthorObjects; try { matchingAuthorObjects = getAuthorObjectsForQuery(String.format("where doc.object(%s).id = '%s'", this.defaultSerializer.serialize(AUTHOR_CLASS), authorId)); } catch (Exception e) { throw new GitHubStatsException(String.format("Failed to find matching author for [%s]", authorId), e); } return importAuthorFromGitHub(gitHub, authorId, emailAddress, matchingAuthorObjects, overwrite); } @Override public List<String> createAuthorFromGitHub(GitHub gitHub, String authorId, String fallbackEmail, boolean overwrite) throws GitHubStatsException { List<String> importedUsers = new ArrayList<>(); // Create the page if it doesn't already exist. try { GHUser user = gitHub.getUser(authorId); String email = user.getEmail(); if (StringUtils.isEmpty(email)) { email = fallbackEmail; } BaseObject authorObject = importAuthorInternal(user.getLogin(), email, Collections.EMPTY_LIST, overwrite); if (authorObject != null) { importedUsers.addAll(importAuthorFromGitHub(user, Arrays.asList(authorObject), overwrite)); } } catch (Exception e) { throw new GitHubStatsException("Failed to import or create author from GitHub", e); } return importedUsers; } @Override public List<String> importAllCommittersFromGitHub(GitHub gitHub) throws GitHubStatsException { Set<String> importedUsers = new LinkedHashSet<>(); // Find all Git repositories defined in the current wiki. Map<GitHubRepository, String> repositories = getAllRepositoryURLs(); for (Map.Entry<GitHubRepository, String> entry : repositories.entrySet()) { importedUsers.addAll(importCommittersFromGitHub(gitHub, entry.getKey())); } return new ArrayList<>(importedUsers); } @Override public List<String> importCommittersFromGitHub(GitHub gitHub, GitHubRepository repository) throws GitHubStatsException { List<String> importedUsers = new ArrayList<>(); // Find all collaborators for the specified repository XWikiContext xcontext = getXWikiContext(); try { GHRepository ghRepository = gitHub.getRepository( String.format("%s/%s", repository.getOrganizationId(), repository.getRepositoryId())); for (GHUser user : ghRepository.getCollaborators()) { // Ideally we would get the user email from GitHub and update that record. However a lot of users don't // specify their email address on GitHub. Thus we use a different strategy: // - Look for all users who have an id matching the GitHub user id and update them, hoping that no two // users have the same id... // - If no matching author is found, create a new entry List<BaseObject> matchingAuthorObjects = getAuthorObjectsForQuery( String.format("where doc.object(%s).id = '%s'", this.defaultSerializer.serialize(AUTHOR_CLASS), user.getLogin())); if (matchingAuthorObjects.isEmpty()) { // Create new author entry BaseObject authorObject = importAuthorInternal(user.getLogin(), user.getEmail(), Collections.singleton(repository), false); // Fill it with author data from GitHub importedUsers .addAll(importAuthorFromGitHub(user, Collections.singletonList(authorObject), false)); } else { for (BaseObject matchingAuthorObject : matchingAuthorObjects) { XWikiDocument authorDocument = matchingAuthorObject.getOwnerDocument(); // Find the xobject representing that repository and if it doesn't exist, create it! BaseObject foundRepositoryObject = null; List<BaseObject> baseObjects = authorDocument.getXObjects(AUTHOR_REPOSITORY_CLASS); if (baseObjects != null) { for (BaseObject baseObject : baseObjects) { // XObjects can be null since there can be holes, just ignore if (baseObject == null) { continue; } if (repository.getOrganizationId() .equals(baseObject.getStringValue("organizationId")) && repository.getRepositoryId() .equals(baseObject.getStringValue("repositoryId"))) { foundRepositoryObject = baseObject; break; } } } if (foundRepositoryObject == null) { // Create xobject foundRepositoryObject = authorDocument.newXObject(AUTHOR_REPOSITORY_CLASS, xcontext); foundRepositoryObject.setStringValue("organizationId", repository.getOrganizationId()); foundRepositoryObject.setStringValue("repositoryId", repository.getRepositoryId()); } int currentCommitterValue = foundRepositoryObject.getIntValue("committer"); if (currentCommitterValue != 1) { foundRepositoryObject.setIntValue("committer", 1); // Save modifications xcontext.getWiki().saveDocument(authorDocument, "Imported committer status from GitHub", true, xcontext); importedUsers.add(authorDocument.getDocumentReference().getName()); } } } } } catch (Exception e) { throw new GitHubStatsException("Failed to import Committer data from GitHub", e); } return importedUsers; } @Override public List<String> linkAuthors() throws GitHubStatsException { Set<String> modifiedUsers = new LinkedHashSet<>(); // For each author that has its user avatar field set look for similar authors and fill mutually missing fields // using the following strategies: // - A - find other authors with the same id (but different emails) // - B - find other authors having an id matching the author name // - C - find other authors having the same name // - D - find other authors having the same email // - E - find other authors having a full name matching the id try { List<String> linkedUsers = new ArrayList<>(); do { linkedUsers.clear(); String authorClassReference = this.defaultSerializer.serialize(AUTHOR_CLASS); // Note that the query is made to work with Oracle which treats empty strings as null. List<BaseObject> fullAuthorObjects = getAuthorObjectsForQuery(String.format( ", doc.object(%s) author where (author.avatar <> '' or (author.avatar is not null and '' is null))", authorClassReference)); for (BaseObject fullAuthorObject : fullAuthorObjects) { String gitId = fullAuthorObject.getStringValue("id"); String gitName = fullAuthorObject.getStringValue("name"); String gitEmail = fullAuthorObject.getStringValue("email"); // Strategy A if (!StringUtils.isEmpty(gitId)) { List<BaseObject> authorObjects = getAuthorObjectsForQuery(String.format( ", doc.object(%s) author where author.id = '%s' and author.email <> '%s'", authorClassReference, gitId, gitEmail)); List<String> results = linkUser(fullAuthorObject, authorObjects); linkedUsers.addAll(results); modifiedUsers.addAll(results); } // Strategy B if (!StringUtils.isEmpty(gitName)) { List<BaseObject> authorObjects = getAuthorObjectsForQuery( String.format("where doc.object(%s).id = '%s'", authorClassReference, gitName)); List<String> results = linkUser(fullAuthorObject, authorObjects); linkedUsers.addAll(results); modifiedUsers.addAll(results); } // Strategy C if (!StringUtils.isEmpty(gitName)) { List<BaseObject> authorObjects = getAuthorObjectsForQuery(String.format( ", doc.object(%s) author where author.name = '%s' and author.id <> '%s' and " + "author.email <> '%s'", authorClassReference, gitName, gitId, gitEmail)); List<String> results = linkUser(fullAuthorObject, authorObjects); linkedUsers.addAll(results); modifiedUsers.addAll(results); } // Strategy D if (!StringUtils.isEmpty(gitEmail)) { List<BaseObject> authorObjects = getAuthorObjectsForQuery(String.format( ", doc.object(%s) author where author.email = '%s' and author.id <> '%s'", authorClassReference, gitEmail, gitId)); List<String> results = linkUser(fullAuthorObject, authorObjects); linkedUsers.addAll(results); modifiedUsers.addAll(results); } // Strategy E if (!StringUtils.isEmpty(gitEmail)) { List<BaseObject> authorObjects = getAuthorObjectsForQuery(String.format( ", doc.object(%s) author where author.name = '%s' and author.id <> '%s'", authorClassReference, gitId, gitId)); List<String> results = linkUser(fullAuthorObject, authorObjects); linkedUsers.addAll(results); modifiedUsers.addAll(results); } } } while (!linkedUsers.isEmpty()); } catch (Exception e) { throw new GitHubStatsException("Failed to link authors", e); } return new ArrayList<>(modifiedUsers); } @Override public Map<Author, Map<String, ?>> getAuthorsForRepositories(Collection<GitHubRepository> repositories) throws GitHubStatsException { Map<Author, Map<String, ?>> authors = new HashMap<>(); try { // Find all authors for the passed repositories List<String> whereConditions = new ArrayList<>(); String authorRepositoryClassReference = this.defaultSerializer.serialize(AUTHOR_REPOSITORY_CLASS); for (GitHubRepository repository : repositories) { whereConditions .add(String.format("(authorRepo.organizationId = '%s' AND authorRepo.repositoryId = '%s')", repository.getOrganizationId(), repository.getRepositoryId())); } List<BaseObject> authorObjects = getAuthorObjectsForQuery( String.format(", doc.object(%s) authorRepo where %s", authorRepositoryClassReference, StringUtils.join(whereConditions, " OR "))); // For each author return some author data for (BaseObject authorObject : authorObjects) { String id = authorObject.getStringValue("id"); Author author = new Author(id, authorObject.getStringValue("email")); Map<String, Object> authorData = new HashMap<>(); String name = authorObject.getStringValue("name"); if (StringUtils.isEmpty(name)) { name = id; } authorData.put("name", name); String avatar = authorObject.getStringValue("avatar"); if (avatar != null) { authorData.put("avatar", avatar); } String company = authorObject.getStringValue("company"); if (company != null) { authorData.put("company", company); } authorData.put("committer", isCommitter(authorObject, repositories)); authors.put(author, authorData); } } catch (Exception e) { throw new GitHubStatsException( String.format("Failed to get authors for repositories [%s]", repositories), e); } return authors; } private boolean isCommitter(BaseObject authorObject, Collection<GitHubRepository> repositories) { boolean isCommitter = false; List<BaseObject> authorRepoObjects = authorObject.getOwnerDocument().getXObjects(AUTHOR_REPOSITORY_CLASS); if (authorRepoObjects != null) { for (BaseObject authorRepoObject : authorRepoObjects) { // XObjects can be null since there can be holes, just ignore if (authorRepoObject == null) { continue; } boolean committer = authorRepoObject.getIntValue("committer") == 1; if (committer) { String organizationId = authorRepoObject.getStringValue("organizationId"); String repositoryId = authorRepoObject.getStringValue("repositoryId"); if (repositories.contains(new GitHubRepository(organizationId, repositoryId))) { isCommitter = true; break; } } } } return isCommitter; } @Override public List<String> importRepositoriesFromGitHub(GitHub gitHub, String organizationId, boolean overwrite) throws GitHubStatsException { List<String> importedRepositories = new ArrayList<>(); try { for (GHRepository repository : gitHub.getOrganization(organizationId).getRepositories().values()) { // If the repo has no commit then don't import it since gitective doesn't work with empty git repos. // TODO: Remove once gitective is fixed and doesn't result in a NPE in this case... if (repository.getSize() == 0) { continue; } XWikiContext xcontext = getXWikiContext(); String repositoryAsString = String.format("%s:%s", organizationId, repository.getName()); EntityReference repositoryReference = new EntityReference(repositoryAsString, EntityType.DOCUMENT, new EntityReference(SPACE, EntityType.SPACE)); XWikiDocument repositoryDocument = xcontext.getWiki().getDocument(repositoryReference, xcontext); if (repositoryDocument.isNew() || overwrite) { repositoryDocument.setTitle(String.format("Repository [%s] for Organization [%s]", repository.getName(), organizationId)); repositoryDocument.setParentReference(PARENT); repositoryDocument.setHidden(true); BaseObject repositoryObject = repositoryDocument.getXObject(REPOSITORY_CLASS, true, xcontext); repositoryObject.setStringValue("organization", organizationId); repositoryObject.setStringValue("id", repository.getName()); repositoryObject.setStringValue("giturl", repository.getGitTransportUrl()); repositoryObject.setStringValue("htmlurl", repository.getUrl().toExternalForm()); // Save modifications xcontext.getWiki().saveDocument(repositoryDocument, "Imported repository from GitHub", true, xcontext); importedRepositories.add(repositoryAsString); } } } catch (Exception e) { throw new GitHubStatsException(String .format("Failed to locate Git repositories from GitHub for organization [%s]", organizationId)); } return importedRepositories; } @Override public List<String> deleteRepositories() throws GitHubStatsException { return deleteItems(REPOSITORY_CLASS, "Failed to delete some GitHub repository pages"); } @Override public List<String> deleteAuthors() throws GitHubStatsException { return deleteItems(AUTHOR_CLASS, "Failed to delete some GitHub author pages"); } @Override public Map<GitHubRepository, String> getRepositoryURLs(String... repositoriesAsStrings) throws GitHubStatsException { Map<GitHubRepository, String> result = new HashMap<>(); Map<GitHubRepository, String> allRepos = getAllRepositoryURLs(); // For each defined repo, verify it matches the passed string. If it doesn't remove it from the list! for (Map.Entry<GitHubRepository, String> repoEntry : allRepos.entrySet()) { GitHubRepository repository = repoEntry.getKey(); for (String repositoryAsString : repositoriesAsStrings) { String[] tokens = StringUtils.split(repositoryAsString, '/'); if (("*".equals(tokens[1]) && repository.getOrganizationId().equals(tokens[0])) || (repository.getOrganizationId().equals(tokens[0]) && repository.getRepositoryId().equals(tokens[1]))) { result.put(repository, repoEntry.getValue()); } } } return result; } private Map<GitHubRepository, String> getAllRepositoryURLs() throws GitHubStatsException { // Find all Git repositories defined in the current wiki. List<Object[]> results; try { Query query = this.queryManager.createQuery( String.format("select distinct repo.organization, repo.id, repo.giturl from Document doc, " + "doc.object(%s) as repo", this.defaultSerializer.serialize(REPOSITORY_CLASS)), Query.XWQL); results = query.execute(); } catch (QueryException e) { throw new GitHubStatsException("Failed to locate GitHub repositories objects in the wiki", e); } // For each repository found, find all authors that have ever contributed code. Map<GitHubRepository, String> repositories = new HashMap<>(); for (Object[] repoData : results) { String organizationId = (String) repoData[0]; String repositoryId = (String) repoData[1]; String gitURL = (String) repoData[2]; repositories.put(new GitHubRepository(organizationId, repositoryId), gitURL); } return repositories; } @Override public Map<GitHubRepository, String> getRepositoryURLs(List<GitHubRepository> repositories) throws GitHubStatsException { Map<GitHubRepository, String> result = new HashMap<>(); Map<GitHubRepository, String> repos = getAllRepositoryURLs(); for (Map.Entry<GitHubRepository, String> repoEntry : repos.entrySet()) { GitHubRepository gitHubRepository = repoEntry.getKey(); if (repositories.contains(gitHubRepository)) { result.put(gitHubRepository, repoEntry.getValue()); } } return result; } @Override public List<Repository> getRepositories(Map<GitHubRepository, String> repositories) { List<Repository> result = new ArrayList<>(); for (Map.Entry<GitHubRepository, String> repoEntry : repositories.entrySet()) { result.add(getRepository(repoEntry.getValue(), repoEntry.getKey())); } return result; } @Override public Map<String, Map<String, Object>> aggregateCommitsPerAuthor(UserCommitActivity[] userCommitActivity, Map<Author, Map<String, Object>> authors) { if (authors == null) { return Collections.emptyMap(); } Map<String, Map<String, Object>> result = new HashMap<>(); Map<String, Set<Author>> authorsByName = extractAuthorsByName(authors); for (UserCommitActivity userCommit : userCommitActivity) { Author author = new Author(userCommit.getName(), userCommit.getEmail()); Map<String, Object> authorData = authors.get(author); if (authorData == null) { // If we don't know this author, we skip it continue; } String authorName = (String) authorData.get("name"); if (StringUtils.isEmpty(authorName)) { authorName = userCommit.getName(); } // Create a result entry if none exist for the name already Map<String, Object> authorResult = result.get(authorName); if (authorResult == null) { authorResult = new HashMap<>(); result.put(authorName, authorResult); // Add all the authors with the same name as duplicates Set<Author> contributingAuthors = authorsByName.get(authorName); authorResult.put("authors", contributingAuthors); // Save the email too authorResult.put("email", author.getEmail()); } else { // Add this author as an aggregated author Set<Author> contributingAuthors = (Set<Author>) authorResult.get("authors"); contributingAuthors.add(author); } // If the avatar or company fields are not set already, set them! String avatar = (String) authorResult.get("avatar"); if (StringUtils.isEmpty(avatar)) { authorResult.put("avatar", authorData.get("avatar")); } String company = (String) authorResult.get("company"); if (StringUtils.isEmpty(company)) { authorResult.put("company", authorData.get("company")); } // Overwrite committer info if true if ((Boolean) authorData.get("committer")) { authorResult.put("committer", true); } else { authorResult.put("committer", false); } // Increase counter and store aggregated result Integer counter = (Integer) authorResult.get("count"); if (counter == null) { counter = 0; } counter += userCommit.getCount(); authorResult.put("count", counter); } // Also add all authors with same email addresses as aggregated authors. Map<String, Set<Author>> authorsByEmail = extractAuthorsByEmail(authors); for (Map.Entry<String, Map<String, Object>> entry : result.entrySet()) { Map<String, Object> resultData = entry.getValue(); String email = (String) resultData.get("email"); Set<Author> authorByEmail = authorsByEmail.get(email); Set<Author> contributingAuthors = (Set<Author>) resultData.get("authors"); contributingAuthors.addAll(authorByEmail); } // Sort Map List<Map.Entry<String, Map<String, Object>>> list = new ArrayList<>(result.entrySet()); Collections.sort(list, (e1, e2) -> { // Highest count first! Integer count1 = (Integer) e1.getValue().get("count"); Integer count2 = (Integer) e2.getValue().get("count"); return count2.compareTo(count1); }); Map<String, Map<String, Object>> sortedResult = new LinkedHashMap<>(); for (Map.Entry<String, Map<String, Object>> entry : list) { sortedResult.put(entry.getKey(), entry.getValue()); } return sortedResult; } private Map<String, Set<Author>> extractAuthorsByName(Map<Author, Map<String, Object>> authors) { if (authors == null) { return Collections.emptyMap(); } Map<String, Set<Author>> authorsByName = new HashMap<>(); for (Map.Entry<Author, Map<String, Object>> entry : authors.entrySet()) { Author author = entry.getKey(); Map<String, Object> authorData = entry.getValue(); String name = (String) authorData.get("name"); Set<Author> authorByName = authorsByName.get(name); if (authorByName == null) { authorByName = new HashSet<>(); authorsByName.put(name, authorByName); } authorByName.add(author); } return authorsByName; } private Map<String, Set<Author>> extractAuthorsByEmail(Map<Author, Map<String, Object>> authors) { if (authors == null) { return Collections.emptyMap(); } Map<String, Set<Author>> authorsByEmail = new HashMap<>(); for (Map.Entry<Author, Map<String, Object>> entry : authors.entrySet()) { Author author = entry.getKey(); Set<Author> authorByEmail = authorsByEmail.get(author.getEmail()); if (authorByEmail == null) { authorByEmail = new HashSet<>(); authorsByEmail.put(author.getEmail(), authorByEmail); } authorByEmail.add(author); } return authorsByEmail; } private List<String> deleteItems(EntityReference xclassReference, String exceptionMessage) throws GitHubStatsException { List<String> deletedItems = new ArrayList<>(); try { Query query = this.queryManager.createQuery( String.format("select distinct doc.space, doc.name from Document doc, doc.object(%s) as author", this.defaultSerializer.serialize(xclassReference)), Query.XWQL); List<Object[]> results = query.execute(); XWikiContext xcontext = getXWikiContext(); for (Object[] documentData : results) { EntityReference relativeReference = new EntityReference((String) documentData[1], EntityType.DOCUMENT, new EntityReference((String) documentData[0], EntityType.SPACE)); XWikiDocument itemDocument = xcontext.getWiki().getDocument(relativeReference, xcontext); deletedItems.add(itemDocument.getDocumentReference().toString()); xcontext.getWiki().deleteDocument(itemDocument, true, xcontext); } } catch (Exception e) { throw new GitHubStatsException(exceptionMessage, e); } return deletedItems; } private List<String> linkUser(BaseObject fullAuthorObject, List<BaseObject> authorObjects) throws XWikiException { List<String> linkedUsers = new ArrayList<>(); XWikiContext xcontext = getXWikiContext(); for (BaseObject authorObject : authorObjects) { boolean modified = false; modified = modified || setField("avatar", fullAuthorObject, authorObject); modified = modified || setField("name", fullAuthorObject, authorObject); modified = modified || setField("profileurl", fullAuthorObject, authorObject); modified = modified || setField("company", fullAuthorObject, authorObject); // Also set the committer flag on repos Map<GitHubRepository, BaseObject> fullRepos = getAuthorRepositories(fullAuthorObject); Map<GitHubRepository, BaseObject> repos = getAuthorRepositories(authorObject); for (Map.Entry<GitHubRepository, BaseObject> repoEntry : repos.entrySet()) { // If this repo is in fullRepos and the fullAuthor is a committer then set it! BaseObject fullRepoObject = fullRepos.get(repoEntry.getKey()); if (fullRepoObject != null) { boolean isCommitter = fullRepoObject.getIntValue("committer") == 1; if (isCommitter && repoEntry.getValue().getIntValue("committer") != 1) { repoEntry.getValue().setIntValue("committer", 1); modified = true; } } } if (modified) { String fullId = fullAuthorObject.getStringValue("id"); // Save modifications xcontext.getWiki().saveDocument(authorObject.getOwnerDocument(), String.format("Linked author with [%s]", fullId), true, xcontext); linkedUsers.add(authorObject.getDocumentReference().getName()); } } return linkedUsers; } private Map<GitHubRepository, BaseObject> getAuthorRepositories(BaseObject authorObject) { Map<GitHubRepository, BaseObject> repos = new HashMap<>(); List<BaseObject> repoObjects = authorObject.getOwnerDocument().getXObjects(AUTHOR_REPOSITORY_CLASS); if (repoObjects != null) { for (BaseObject repoObject : repoObjects) { if (repoObject == null) { continue; } String organizationId = repoObject.getStringValue("organizationId"); String repositoryId = repoObject.getStringValue("repositoryId"); repos.put(new GitHubRepository(organizationId, repositoryId), repoObject); } } return repos; } private boolean setField(String fieldName, BaseObject source, BaseObject target) { boolean modified = false; String fullValue = source.getStringValue(fieldName); if (!StringUtils.isEmpty(fullValue)) { String value = target.getStringValue(fieldName); if (StringUtils.isEmpty(value)) { target.setStringValue(fieldName, fullValue); modified = true; } } return modified; } private List<BaseObject> getAuthorObjectsForQuery(String xwqlWhere) throws QueryException, XWikiException { List<BaseObject> objects = new ArrayList<>(); Query query = this.queryManager.createQuery( String.format("select distinct doc.space, doc.name from Document doc %s", xwqlWhere), Query.XWQL); List<Object[]> results = query.execute(); XWikiContext xcontext = getXWikiContext(); for (Object[] documentData : results) { EntityReference relativeReference = new EntityReference((String) documentData[1], EntityType.DOCUMENT, new EntityReference((String) documentData[0], EntityType.SPACE)); XWikiDocument authorDocument = xcontext.getWiki().getDocument(relativeReference, xcontext); BaseObject authorObject = authorDocument.getXObject(AUTHOR_CLASS, false, xcontext); objects.add(authorObject); } return objects; } private XWikiDocument getAuthorDocument(String name, String email) throws XWikiException { String authorAsString = String.format("%s <%s>", name, email); XWikiContext xcontext = getXWikiContext(); EntityReference authorReference = new EntityReference(authorAsString, EntityType.DOCUMENT, new EntityReference(SPACE, EntityType.SPACE)); // Get author document or create a new one if it doesn't exist return xcontext.getWiki().getDocument(authorReference, xcontext); } private BaseObject createAuthorClass(XWikiDocument authorDocument) { return authorDocument.getXObject(AUTHOR_CLASS, true, getXWikiContext()); } private void addAuthorRepositoryObjects(XWikiDocument authorDocument, Collection<GitHubRepository> repositories, XWikiContext xcontext) throws XWikiException { for (GitHubRepository repository : repositories) { BaseObject authorObject = authorDocument.newXObject(AUTHOR_REPOSITORY_CLASS, xcontext); authorObject.setStringValue("organizationId", repository.getOrganizationId()); authorObject.setStringValue("repositoryId", repository.getRepositoryId()); } } private Repository getRepository(String uri, GitHubRepository repository) { return this.gitManager.getRepository(uri, repository.getOrganizationId() + File.separator + repository.getRepositoryId()); } private XWikiContext getXWikiContext() { return (XWikiContext) this.execution.getContext().getProperty(XWikiContext.EXECUTIONCONTEXT_KEY); } }