/*******************************************************************************
* Copyright (c) 2011 Red Hat and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* David Green <david.green@tasktop.com> - initial contribution
* Christian Trutz <christian.trutz@gmail.com> - initial contribution
* Chris Aniszczyk <caniszczyk@gmail.com> - initial contribution
*******************************************************************************/
package org.eclipse.mylyn.internal.github.core.issue;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.egit.github.core.Comment;
import org.eclipse.egit.github.core.IRepositoryIdProvider;
import org.eclipse.egit.github.core.Issue;
import org.eclipse.egit.github.core.Label;
import org.eclipse.egit.github.core.Milestone;
import org.eclipse.egit.github.core.PullRequest;
import org.eclipse.egit.github.core.Repository;
import org.eclipse.egit.github.core.RepositoryId;
import org.eclipse.egit.github.core.client.GitHubClient;
import org.eclipse.egit.github.core.service.IssueService;
import org.eclipse.egit.github.core.service.LabelService;
import org.eclipse.egit.github.core.service.MilestoneService;
import org.eclipse.egit.github.core.util.LabelComparator;
import org.eclipse.egit.github.core.util.MilestoneComparator;
import org.eclipse.mylyn.commons.net.AuthenticationCredentials;
import org.eclipse.mylyn.commons.net.AuthenticationType;
import org.eclipse.mylyn.commons.net.Policy;
import org.eclipse.mylyn.internal.github.core.GitHub;
import org.eclipse.mylyn.internal.github.core.QueryUtils;
import org.eclipse.mylyn.internal.github.core.RepositoryConnector;
import org.eclipse.mylyn.internal.github.core.pr.PullRequestConnector;
import org.eclipse.mylyn.internal.tasks.core.IRepositoryConstants;
import org.eclipse.mylyn.tasks.core.IRepositoryQuery;
import org.eclipse.mylyn.tasks.core.TaskRepository;
import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler;
import org.eclipse.mylyn.tasks.core.data.TaskData;
import org.eclipse.mylyn.tasks.core.data.TaskDataCollector;
import org.eclipse.mylyn.tasks.core.sync.ISynchronizationSession;
/**
* GitHub issue repository connector.
*/
public class IssueConnector extends RepositoryConnector {
/**
* GitHub kind.
*/
public static final String KIND = GitHub.CONNECTOR_KIND;
/**
* Get repository label for id provider
*
* @param repo
* @return label
*/
public static String getRepositoryLabel(IRepositoryIdProvider repo) {
return repo.generateId() + Messages.IssueConnector_LabelIssues;
}
/**
* Create issue task repository
*
* @param repo
* @param username
* @param password
* @return task repository
*/
public static TaskRepository createTaskRepository(Repository repo,
String username, String password) {
String url = GitHub.createGitHubUrl(repo.getOwner().getLogin(),
repo.getName());
TaskRepository repository = new TaskRepository(KIND, url);
repository.setProperty(IRepositoryConstants.PROPERTY_LABEL,
getRepositoryLabel(repo));
if (username != null && password != null)
repository.setCredentials(AuthenticationType.REPOSITORY,
new AuthenticationCredentials(username, password), true);
repository.setProperty(IRepositoryConstants.PROPERTY_CATEGORY,
IRepositoryConstants.CATEGORY_BUGS);
return repository;
}
/**
* Create client for repository
*
* @param repository
* @return client
*/
public static GitHubClient createClient(TaskRepository repository) {
GitHubClient client = GitHubClient.createClient(repository
.getRepositoryUrl());
GitHub.addCredentials(client, repository);
return GitHub.configureClient(client);
}
/**
* GitHub specific {@link AbstractTaskDataHandler}.
*/
private final IssueTaskDataHandler taskDataHandler;
private final Map<TaskRepository, List<Label>> repositoryLabels = Collections
.synchronizedMap(new HashMap<TaskRepository, List<Label>>());
private final Map<TaskRepository, List<Milestone>> repositoryMilestones = Collections
.synchronizedMap(new HashMap<TaskRepository, List<Milestone>>());
/**
* Create GitHub issue repository connector
*/
public IssueConnector() {
taskDataHandler = new IssueTaskDataHandler(this);
}
/**
* Refresh labels for repository
*
* @param repository
* @return labels
* @throws CoreException
*/
public List<Label> refreshLabels(TaskRepository repository)
throws CoreException {
Assert.isNotNull(repository, "Repository cannot be null"); //$NON-NLS-1$
RepositoryId repo = GitHub.getRepository(repository.getRepositoryUrl());
GitHubClient client = createClient(repository);
LabelService service = new LabelService(client);
try {
List<Label> labels = service.getLabels(repo.getOwner(),
repo.getName());
Collections.sort(labels, new LabelComparator());
this.repositoryLabels.put(repository, labels);
return labels;
} catch (IOException e) {
throw new CoreException(GitHub.createWrappedStatus(e));
}
}
/**
* Get labels for task repository.
*
* @param repository
* @return non-null but possibly empty list of labels
*/
public List<Label> getLabels(TaskRepository repository) {
Assert.isNotNull(repository, "Repository cannot be null"); //$NON-NLS-1$
List<Label> labels = new LinkedList<Label>();
List<Label> cached = this.repositoryLabels.get(repository);
if (cached != null)
labels.addAll(cached);
return labels;
}
/**
* Are there cached labels for the specified task repository?
*
* @param repository
* @return true if contains labels, false otherwise
*/
public boolean hasCachedLabels(TaskRepository repository) {
return repositoryLabels.containsKey(repository);
}
/**
* Refresh milestones for repository
*
* @param repository
* @return milestones
* @throws CoreException
*/
public List<Milestone> refreshMilestones(TaskRepository repository)
throws CoreException {
Assert.isNotNull(repository, "Repository cannot be null"); //$NON-NLS-1$
RepositoryId repo = GitHub.getRepository(repository.getRepositoryUrl());
GitHubClient client = createClient(repository);
MilestoneService service = new MilestoneService(client);
try {
List<Milestone> milestones = new LinkedList<Milestone>();
milestones.addAll(service.getMilestones(repo.getOwner(),
repo.getName(), IssueService.STATE_OPEN));
milestones.addAll(service.getMilestones(repo.getOwner(),
repo.getName(), IssueService.STATE_CLOSED));
Collections.sort(milestones, new MilestoneComparator());
repositoryMilestones.put(repository, milestones);
return milestones;
} catch (IOException e) {
throw new CoreException(GitHub.createWrappedStatus(e));
}
}
/**
* Get milestones for task repository.
*
* @param repository
* @return non-null but possibly empty list of milestones
*/
public List<Milestone> getMilestones(TaskRepository repository) {
Assert.isNotNull(repository, "Repository cannot be null"); //$NON-NLS-1$
List<Milestone> milestones = new LinkedList<Milestone>();
List<Milestone> cached = this.repositoryMilestones.get(repository);
if (cached != null)
milestones.addAll(cached);
return milestones;
}
/**
* Are there cached milestones for the specified task repository?
*
* @param repository
* @return true if contains milestones, false otherwise
*/
public boolean hasCachedMilestones(TaskRepository repository) {
return repositoryMilestones.containsKey(repository);
}
/**
* {@inheritDoc}
*
* @return always {@code true}
*/
@Override
public boolean canCreateNewTask(TaskRepository repository) {
return true;
}
/**
* {@inheritDoc}
*
* @return always {@code true}
*/
@Override
public boolean canCreateTaskFromKey(TaskRepository repository) {
return true;
}
/**
* {@inheritDoc}
*
* @see #KIND
*/
@Override
public String getConnectorKind() {
return KIND;
}
/**
* {@inheritDoc}
*/
@Override
public String getLabel() {
return Messages.IssueConnector_LabelConnector;
}
/**
* {@inheritDoc}
*/
@Override
public AbstractTaskDataHandler getTaskDataHandler() {
return taskDataHandler;
}
@Override
public IStatus performQuery(TaskRepository repository,
IRepositoryQuery query, TaskDataCollector collector,
ISynchronizationSession session, IProgressMonitor monitor) {
IStatus result = Status.OK_STATUS;
List<String> statuses = QueryUtils.getAttributes(
IssueService.FILTER_STATE, query);
monitor.beginTask(Messages.IssueConector_TaskQuerying, statuses.size());
try {
RepositoryId repo = GitHub.getRepository(repository
.getRepositoryUrl());
GitHubClient client = createClient(repository);
IssueService service = new IssueService(client);
Map<String, String> filterData = new HashMap<String, String>();
String mentions = query.getAttribute(IssueService.FILTER_MENTIONED);
if (mentions != null)
filterData.put(IssueService.FILTER_MENTIONED, mentions);
String assignee = query.getAttribute(IssueService.FILTER_ASSIGNEE);
if (assignee != null)
filterData.put(IssueService.FILTER_ASSIGNEE, assignee);
String milestone = query
.getAttribute(IssueService.FILTER_MILESTONE);
if (milestone != null)
filterData.put(IssueService.FILTER_MILESTONE, milestone);
List<String> labels = QueryUtils.getAttributes(
IssueService.FILTER_LABELS, query);
if (!labels.isEmpty()) {
StringBuilder labelsQuery = new StringBuilder();
for (String label : labels)
labelsQuery.append(label).append(',');
filterData.put(IssueService.FILTER_LABELS,
labelsQuery.toString());
}
String owner = repo.getOwner();
String name = repo.getName();
for (String status : statuses) {
filterData.put(IssueService.FILTER_STATE, status);
List<Issue> issues = service.getIssues(repo.getOwner(),
repo.getName(), filterData);
// collect task data
for (Issue issue : issues) {
if (isPullRequest(issue))
continue;
List<Comment> comments = null;
if (issue.getComments() > 0)
comments = service.getComments(owner, name,
Integer.toString(issue.getNumber()));
TaskData taskData = taskDataHandler.createTaskData(
repository, monitor, owner, name, issue, comments);
collector.accept(taskData);
}
monitor.worked(1);
}
} catch (IOException e) {
result = GitHub.createWrappedStatus(e);
}
monitor.done();
return result;
}
private boolean isPullRequest(Issue issue) {
PullRequest request = issue.getPullRequest();
return request != null && request.getDiffUrl() != null;
}
@Override
public TaskData getTaskData(TaskRepository repository, String taskId,
IProgressMonitor monitor) throws CoreException {
RepositoryId repo = GitHub.getRepository(repository.getRepositoryUrl());
try {
GitHubClient client = createClient(repository);
IssueService service = new IssueService(client);
Issue issue = service.getIssue(repo.getOwner(), repo.getName(),
taskId);
if (isPullRequest(issue))
return null;
List<Comment> comments = null;
if (issue.getComments() > 0)
comments = service.getComments(repo.getOwner(), repo.getName(),
taskId);
return taskDataHandler.createTaskData(repository, monitor,
repo.getOwner(), repo.getName(), issue, comments);
} catch (IOException e) {
throw new CoreException(GitHub.createWrappedStatus(e));
}
}
@Override
public String getRepositoryUrlFromTaskUrl(String taskFullUrl) {
if (taskFullUrl != null) {
Matcher matcher = Pattern.compile(
"(http://.+?)/issues/([^/]+)").matcher(taskFullUrl); //$NON-NLS-1$
if (matcher.matches())
return matcher.group(1);
}
return null;
}
@Override
public String getTaskIdFromTaskUrl(String taskFullUrl) {
if (taskFullUrl != null) {
Matcher matcher = Pattern
.compile(".+?/issues/([^/]+)").matcher(taskFullUrl); //$NON-NLS-1$
if (matcher.matches())
return matcher.group(1);
}
return null;
}
@Override
public String getTaskUrl(String repositoryUrl, String taskId) {
return repositoryUrl + "/issues/" + taskId; //$NON-NLS-1$
}
@Override
public void updateRepositoryConfiguration(TaskRepository taskRepository,
IProgressMonitor monitor) throws CoreException {
monitor = Policy.monitorFor(monitor);
monitor.beginTask("", 2); //$NON-NLS-1$
monitor.setTaskName(Messages.IssueConnector_TaskUpdatingLabels);
refreshLabels(taskRepository);
monitor.worked(1);
monitor.setTaskName(Messages.IssueConnector_TaskUpdatingMilestones);
refreshMilestones(taskRepository);
monitor.done();
}
}