From 8758fb2dd13ed6031ead593e4fc092c9c2e7f1eb Mon Sep 17 00:00:00 2001 From: Chris Aniszczyk Date: Wed, 23 Mar 2011 18:04:34 -0500 Subject: Initial commit of Mylyn GitHub Integration Signed-off-by: Chris Aniszczyk --- org.eclipse.mylyn.github.core/.classpath | 7 + org.eclipse.mylyn.github.core/.gitignore | 2 + org.eclipse.mylyn.github.core/.project | 28 ++ .../.settings/org.eclipse.core.resources.prefs | 3 + .../.settings/org.eclipse.core.runtime.prefs | 3 + .../.settings/org.eclipse.jdt.core.prefs | 12 + org.eclipse.mylyn.github.core/META-INF/MANIFEST.MF | 13 + org.eclipse.mylyn.github.core/build.properties | 3 + org.eclipse.mylyn.github.core/pom.xml | 25 + .../org/eclipse/mylyn/github/internal/GitHub.java | 96 ++++ .../mylyn/github/internal/GitHubCredentials.java | 44 ++ .../eclipse/mylyn/github/internal/GitHubIssue.java | 172 +++++++ .../mylyn/github/internal/GitHubIssues.java | 31 ++ .../github/internal/GitHubRepositoryConnector.java | 217 ++++++++ .../mylyn/github/internal/GitHubService.java | 557 +++++++++++++++++++++ .../github/internal/GitHubServiceException.java | 58 +++ .../mylyn/github/internal/GitHubShowIssue.java | 26 + .../github/internal/GitHubTaskAttributeMapper.java | 49 ++ .../github/internal/GitHubTaskAttributes.java | 71 +++ .../github/internal/GitHubTaskDataHandler.java | 265 ++++++++++ .../mylyn/github/internal/GitHubTaskOperation.java | 48 ++ .../github/internal/PermissionDeniedException.java | 37 ++ 22 files changed, 1767 insertions(+) create mode 100644 org.eclipse.mylyn.github.core/.classpath create mode 100644 org.eclipse.mylyn.github.core/.gitignore create mode 100644 org.eclipse.mylyn.github.core/.project create mode 100644 org.eclipse.mylyn.github.core/.settings/org.eclipse.core.resources.prefs create mode 100644 org.eclipse.mylyn.github.core/.settings/org.eclipse.core.runtime.prefs create mode 100644 org.eclipse.mylyn.github.core/.settings/org.eclipse.jdt.core.prefs create mode 100644 org.eclipse.mylyn.github.core/META-INF/MANIFEST.MF create mode 100644 org.eclipse.mylyn.github.core/build.properties create mode 100644 org.eclipse.mylyn.github.core/pom.xml create mode 100644 org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHub.java create mode 100644 org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubCredentials.java create mode 100644 org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubIssue.java create mode 100644 org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubIssues.java create mode 100644 org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubRepositoryConnector.java create mode 100644 org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubService.java create mode 100644 org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubServiceException.java create mode 100644 org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubShowIssue.java create mode 100644 org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskAttributeMapper.java create mode 100644 org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskAttributes.java create mode 100644 org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskDataHandler.java create mode 100644 org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskOperation.java create mode 100644 org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/PermissionDeniedException.java (limited to 'org.eclipse.mylyn.github.core') diff --git a/org.eclipse.mylyn.github.core/.classpath b/org.eclipse.mylyn.github.core/.classpath new file mode 100644 index 00000000..64c5e31b --- /dev/null +++ b/org.eclipse.mylyn.github.core/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/org.eclipse.mylyn.github.core/.gitignore b/org.eclipse.mylyn.github.core/.gitignore new file mode 100644 index 00000000..d567ba01 --- /dev/null +++ b/org.eclipse.mylyn.github.core/.gitignore @@ -0,0 +1,2 @@ +bin +target diff --git a/org.eclipse.mylyn.github.core/.project b/org.eclipse.mylyn.github.core/.project new file mode 100644 index 00000000..3ae827bb --- /dev/null +++ b/org.eclipse.mylyn.github.core/.project @@ -0,0 +1,28 @@ + + + org.eclipse.mylyn.github.core + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/org.eclipse.mylyn.github.core/.settings/org.eclipse.core.resources.prefs b/org.eclipse.mylyn.github.core/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000..315bf27c --- /dev/null +++ b/org.eclipse.mylyn.github.core/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Tue Jun 09 20:22:30 CEST 2009 +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/org.eclipse.mylyn.github.core/.settings/org.eclipse.core.runtime.prefs b/org.eclipse.mylyn.github.core/.settings/org.eclipse.core.runtime.prefs new file mode 100644 index 00000000..5b9dcff1 --- /dev/null +++ b/org.eclipse.mylyn.github.core/.settings/org.eclipse.core.runtime.prefs @@ -0,0 +1,3 @@ +#Tue Jun 09 20:22:30 CEST 2009 +eclipse.preferences.version=1 +line.separator=\n diff --git a/org.eclipse.mylyn.github.core/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.mylyn.github.core/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..1d6b3c18 --- /dev/null +++ b/org.eclipse.mylyn.github.core/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,12 @@ +#Fri Jul 30 10:22:35 CEST 2010 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.5 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.5 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.5 diff --git a/org.eclipse.mylyn.github.core/META-INF/MANIFEST.MF b/org.eclipse.mylyn.github.core/META-INF/MANIFEST.MF new file mode 100644 index 00000000..f05d1b0d --- /dev/null +++ b/org.eclipse.mylyn.github.core/META-INF/MANIFEST.MF @@ -0,0 +1,13 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: EGit Mylyn GitHub Core Plug-in (Incubation) +Bundle-SymbolicName: org.eclipse.mylyn.github.core +Bundle-Version: 0.1.0.qualifier +Bundle-Vendor: Eclipse EGit +Bundle-RequiredExecutionEnvironment: J2SE-1.5 +Export-Package: org.eclipse.mylyn.github.internal;x-friends:="org.eclipse.mylyn.github.ui" +Require-Bundle: org.eclipse.core.runtime;bundle-version="3.5.0", + org.eclipse.mylyn.tasks.core;bundle-version="3.2.0", + org.eclipse.mylyn.commons.net;bundle-version="3.2.0" +Import-Package: com.google.gson;version="1.6.0", + org.apache.commons.logging diff --git a/org.eclipse.mylyn.github.core/build.properties b/org.eclipse.mylyn.github.core/build.properties new file mode 100644 index 00000000..e1e6580a --- /dev/null +++ b/org.eclipse.mylyn.github.core/build.properties @@ -0,0 +1,3 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/ \ No newline at end of file diff --git a/org.eclipse.mylyn.github.core/pom.xml b/org.eclipse.mylyn.github.core/pom.xml new file mode 100644 index 00000000..12b22700 --- /dev/null +++ b/org.eclipse.mylyn.github.core/pom.xml @@ -0,0 +1,25 @@ + + + + + + org.eclipse.mylyn.github + github-parent + 0.1.0-SNAPSHOT + + + 4.0.0 + core + eclipse-plugin + Eclipse EGit Mylyn GitHub Core (Incubation) + diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHub.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHub.java new file mode 100644 index 00000000..708f39c5 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHub.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * 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 - initial contribution + * Christian Trutz - initial contribution + * Chris Aniszczyk - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.github.internal; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.core.runtime.ILog; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; + +public class GitHub { + public static final String BUNDLE_ID = "org.eclipse.mylyn.github.core"; + public static final String CONNECTOR_KIND = "github"; + + public static final String HTTP_WWW_GITHUB_ORG = "http://www.github.org"; + public static final String HTTP_GITHUB_COM = "http://github.com"; + + public static final Pattern URL_PATTERN = Pattern.compile("(?:"+Pattern.quote(HTTP_WWW_GITHUB_ORG)+"|"+Pattern.quote(HTTP_GITHUB_COM)+")/([^/]+)/([^/]+)"); + + public static IStatus createStatus(int severity, String message) { + return new Status(severity, BUNDLE_ID, message); + } + + public static IStatus createStatus(int severity, String message, Throwable e) { + return new Status(severity, BUNDLE_ID, message, e); + } + + public static IStatus createErrorStatus(String message) { + return createStatus(IStatus.ERROR, message); + } + + public static IStatus createErrorStatus(String message, Throwable t) { + return createStatus(IStatus.ERROR, message, t); + } + + public static IStatus createErrorStatus(Throwable e) { + return createStatus(IStatus.ERROR, "Unexpected error: " + + e.getMessage(), e); + } + + public static ILog getLog() { + return Platform.getLog(Platform.getBundle(BUNDLE_ID)); + } + + public static void logError(String message,Throwable t) { + getLog().log(createErrorStatus(message, t)); + } + + public static void logError(Throwable t) { + getLog().log(createErrorStatus(t.getMessage(), t)); + } + + public static String computeTaskRepositoryUser(String repositoryUrl) { + Matcher matcher = URL_PATTERN.matcher(repositoryUrl); + if (matcher.matches()) { + return matcher.group(1); + } + return null; + } + + public static String computeTaskRepositoryProject(String repositoryUrl) { + Matcher matcher = URL_PATTERN.matcher(repositoryUrl); + if (matcher.matches()) { + return matcher.group(2); + } + return null; + } + + /** + * uses github.com + * @see #createGitHubUrlAlternate(String, String) + */ + public static String createGitHubUrl(String user,String project) { + return HTTP_GITHUB_COM+'/'+user+'/'+project; + } + + /** + * Uses www.github.org + * @see #createGitHubUrl(String, String) + */ + public static String createGitHubUrlAlternate(String user,String project) { + return HTTP_WWW_GITHUB_ORG+'/'+user+'/'+project; + } +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubCredentials.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubCredentials.java new file mode 100644 index 00000000..19cd580c --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubCredentials.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * 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 - initial contribution + * Christian Trutz - initial contribution + * Chris Aniszczyk - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.github.internal; + +import org.eclipse.mylyn.commons.net.AuthenticationCredentials; +import org.eclipse.mylyn.commons.net.AuthenticationType; +import org.eclipse.mylyn.tasks.core.TaskRepository; + +public class GitHubCredentials { + private final String username; + private final String apiToken; + + + public GitHubCredentials(String username, String apiToken) { + this.username = username; + this.apiToken = apiToken; + } + + public GitHubCredentials(AuthenticationCredentials credentials) { + this(credentials.getUserName(),credentials.getPassword()); + } + + public static GitHubCredentials create(TaskRepository repository) { + return new GitHubCredentials(repository.getCredentials(AuthenticationType.REPOSITORY)); + } + + public String getUsername() { + return username; + } + public String getApiToken() { + return apiToken; + } + +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubIssue.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubIssue.java new file mode 100644 index 00000000..9a989e00 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubIssue.java @@ -0,0 +1,172 @@ +/******************************************************************************* + * 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 - initial contribution + * Christian Trutz - initial contribution + * Chris Aniszczyk - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.github.internal; + + +/** + * GitHub Issue object to hold all the properties of an individual issue. + */ +public class GitHubIssue { + + private String number; + + private String user; + + private String title; + + private String body; + + /** + * open, closed + */ + private String state; + + private String created_at; + private String updated_at; + private String closed_at; + + /** + * Create a new GitHub Issue Object + * + * @param number + * - GitHub Issue number + * @param user + * - User who the posted issue belongs too. + * @param title + * - Issue title + * @param body + * - The text body of the issue; + */ + public GitHubIssue(final String number, final String user, + final String title, final String body) { + this.number = number; + this.user = user; + this.title = title; + this.body = body; + } + + /** + * Create a GitHub Issue with all parameters set to empty. + */ + public GitHubIssue() { + this.number = ""; + this.user = ""; + this.title = ""; + this.body = ""; + } + + /** + * Getter for the issue number + * + * @return The string representation of the issue number. + */ + public String getNumber() { + return number; + } + + /** + * Set the issues's number + * + * @param number + * - String representation of the number to set to. + */ + public void setNumber(final String number) { + this.number = number; + } + + /** + * Getter for the user name of the issue creator + * + * @return The user name of the person who created the issue + */ + public String getUser() { + return user; + } + + /** + * Set the issue user name to + * + * @param user + * - The user name to set the issue creator to. + */ + public void setUser(final String user) { + this.user = user; + } + + /** + * Getter for the issue Title + * + * @return The title text of this issue + */ + public String getTitle() { + return title; + } + + /** + * @param title + */ + public void setTitle(final String title) { + this.title = title; + } + + /** + * Getter of the body of an issue + * + * @return The text body of the issue + */ + public String getBody() { + return body; + } + + /** + * Setter for the body of an issue + * + * @param body + * - The text body to set for this issue + */ + public void setBody(final String body) { + this.body = body; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public String getCreated_at() { + return created_at; + } + + public void setCreated_at(String created_at) { + this.created_at = created_at; + } + + public String getUpdated_at() { + return updated_at; + } + + public void setUpdated_at(String updated_at) { + this.updated_at = updated_at; + } + + public String getClosed_at() { + return closed_at; + } + + public void setClosed_at(String closed_at) { + this.closed_at = closed_at; + } +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubIssues.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubIssues.java new file mode 100644 index 00000000..02100590 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubIssues.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * 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 - initial contribution + * Christian Trutz - initial contribution + * Chris Aniszczyk - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.github.internal; + +/** + * Container of multiple GitHub Issues, used when returning JSON objects + */ +public class GitHubIssues { + + private GitHubIssue[] issues; + + /** + * Getter for all issues inside this object + * + * @return The array of individual GitHub Issues + */ + public GitHubIssue[] getIssues() { + return issues; + } + +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubRepositoryConnector.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubRepositoryConnector.java new file mode 100644 index 00000000..c263cb9a --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubRepositoryConnector.java @@ -0,0 +1,217 @@ +/******************************************************************************* + * 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 - initial contribution + * Christian Trutz - initial contribution + * Chris Aniszczyk - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.github.internal; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +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.mylyn.tasks.core.AbstractRepositoryConnector; +import org.eclipse.mylyn.tasks.core.IRepositoryQuery; +import org.eclipse.mylyn.tasks.core.ITask; +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.data.TaskMapper; +import org.eclipse.mylyn.tasks.core.sync.ISynchronizationSession; + +/** + * GitHub connector. + */ +public class GitHubRepositoryConnector extends AbstractRepositoryConnector { + + + /** + * GitHub kind. + */ + protected static final String KIND = GitHub.CONNECTOR_KIND; + + /** + * GitHub service which creates, lists, deletes, etc. GitHub tasks. + */ + private final GitHubService service = new GitHubService(); + + /** + * GitHub specific {@link AbstractTaskDataHandler}. + */ + private final GitHubTaskDataHandler taskDataHandler; + + public GitHubRepositoryConnector() { + taskDataHandler = new GitHubTaskDataHandler(this); + } + + /** + * {@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 "GitHub"; + } + + /** + * {@inheritDoc} + */ + @Override + public AbstractTaskDataHandler getTaskDataHandler() { + return this.taskDataHandler; + } + + @Override + public IStatus performQuery(TaskRepository repository, + IRepositoryQuery query, TaskDataCollector collector, + ISynchronizationSession session, IProgressMonitor monitor) { + + IStatus result = Status.OK_STATUS; + String queryStatus = query.getAttribute("status"); + + String[] statuses; + if (queryStatus.equals("all")) { + statuses = new String[] {"open","closed"}; + } else { + statuses = new String[] { queryStatus }; + } + + monitor.beginTask("Querying repository ...", statuses.length); + try { + String user = GitHub.computeTaskRepositoryUser(repository.getUrl()); + String project = GitHub.computeTaskRepositoryProject(repository.getUrl()); + + // perform query + + for (String status: statuses) { + GitHubIssues issues = service.searchIssues(user,project, + status, query + .getAttribute("queryText")); + + // collect task data + for (GitHubIssue issue : issues.getIssues()) { + TaskData taskData = taskDataHandler.createPartialTaskData( + repository, monitor,user, project, issue); + collector.accept(taskData); + } + monitor.worked(1); + } + + result = Status.OK_STATUS; + } catch (GitHubServiceException e) { + result = GitHub.createErrorStatus(e); + } + + monitor.done(); + return result; + } + + + @Override + public TaskData getTaskData(TaskRepository repository, String taskId, + IProgressMonitor monitor) throws CoreException { + + String user = GitHub.computeTaskRepositoryUser(repository.getUrl()); + String project = GitHub.computeTaskRepositoryProject(repository.getUrl()); + + try { + GitHubIssue issue = service.showIssue(user, project, taskId); + TaskData taskData = taskDataHandler.createTaskData(repository, monitor, user, project, issue); + + return taskData; + } catch (GitHubServiceException e) { + throw new CoreException(GitHub.createErrorStatus(e)); + } + } + + + @Override + public String getRepositoryUrlFromTaskUrl(String taskFullUrl) { + if (taskFullUrl != null) { + Matcher matcher = Pattern.compile("(http://.+?)/issues/issue/([^/]+)").matcher(taskFullUrl); + if (matcher.matches()) { + return matcher.group(1); + } + } + return null; + } + + @Override + public String getTaskIdFromTaskUrl(String taskFullUrl) { + if (taskFullUrl != null) { + Matcher matcher = Pattern.compile(".+?/issues/issue/([^/]+)").matcher(taskFullUrl); + if (matcher.matches()) { + return matcher.group(1); + } + } + return null; + } + + @Override + public String getTaskUrl(String repositoryUrl, String taskId) { + return repositoryUrl+"/issues/issue/"+taskId; + } + + @Override + public void updateRepositoryConfiguration(TaskRepository taskRepository, + IProgressMonitor monitor) throws CoreException { + } + + @Override + public boolean hasTaskChanged(TaskRepository repository, ITask task, + TaskData taskData) { + return new TaskMapper(taskData).hasChanges(task); + } + + @Override + public void updateTaskFromTaskData(TaskRepository taskRepository, + ITask task, TaskData taskData) { + if (!taskData.isNew()) { + task.setUrl(getTaskUrl(taskRepository.getUrl(), taskData.getTaskId())); + } + new TaskMapper(taskData).applyTo(task); + } + + public GitHubService getService() { + return service; + } +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubService.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubService.java new file mode 100644 index 00000000..07d884d8 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubService.java @@ -0,0 +1,557 @@ +/******************************************************************************* + * 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 - initial contribution + * Christian Trutz - initial contribution + * Chris Aniszczyk - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.github.internal; + +import java.io.IOException; + +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.HttpException; +import org.apache.commons.httpclient.HttpMethod; +import org.apache.commons.httpclient.HttpStatus; +import org.apache.commons.httpclient.NameValuePair; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.PostMethod; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.google.gson.Gson; + +/** + * Facility to perform API operations on a GitHub issue tracker. + */ +public class GitHubService { + + private static final Log LOG = LogFactory.getLog(GitHubService.class); + + /** + * GitHub Issues API Documentation: http://develop.github.com/p/issues.html + */ + private final String gitURLBase = "https://github.com/api/v2/json/"; + + private final String gitIssueRoot = "issues/"; + private final String gitUserRoot = "user/"; + + private final HttpClient httpClient; + + private final Gson gson; + + /** + * Helper class, describing all of the possible GitHub API actions. + */ + private final static String OPEN = "open/"; // Implemented + private static final String REOPEN = "reopen/"; + private final static String CLOSE = "close/"; + private final static String EDIT = "edit/"; // Implemented + // private final static String VIEW = "view/"; + private final static String SHOW = "show/"; // :user/:repo/:number + private final static String LIST = "list/"; // Implemented + private final static String SEARCH = "search/"; // Implemented + // private final static String REOPEN = "reopen/"; + // private final static String COMMENT = "comment/"; + private final static String ADD_LABEL = "label/add/"; // Implemented + private final static String REMOVE_LABEL = "label/remove/"; // Implemented + + private static final String EMAILS = "emails"; + + + /** + * Constructor, create the client and JSON/Java interface object. + */ + public GitHubService() { + httpClient = new HttpClient(); + gson = new Gson(); + } + + /** + * Verify that the provided credentials are correct + * @param credentials + * + * @return true if and only if the credentials are correct + */ + public boolean verifyCredentials(GitHubCredentials credentials) throws GitHubServiceException { + PostMethod method = null; + + boolean success = false; + + try { + method = new PostMethod(gitURLBase + gitUserRoot + EMAILS); + + // Set the users login and API token + final NameValuePair login = new NameValuePair("login", credentials.getUsername()); + final NameValuePair token = new NameValuePair("token", credentials.getApiToken()); + method.setRequestBody(new NameValuePair[] { login, token }); + + executeMethod(method); + + // if we reach here we know that credentials were good + success = true; + } catch (PermissionDeniedException e) { + // if we provide bad credentials, GitHub will return 403 or 401 + return false; + } catch (GitHubServiceException e) { + throw e; + } catch (final RuntimeException runtimeException) { + throw runtimeException; + } catch (final Exception exception) { + throw new GitHubServiceException(exception); + } finally { + if (method != null) + method.releaseConnection(); + } + return success; + } + + /** + * Search the GitHub Issues API for a given search term + * + * @param user + * - The user the repository is owned by + * @param repo + * - The Git repository where the issue tracker is hosted + * @param state + * - The issue state you want to filter your search by + * @param searchTerm + * - The text search term to find in the issues. + * + * @return A GitHubIssues object containing all issues from the search + * results + * + * @throws GitHubServiceException + * + * @note API Doc: /issues/search/:user/:repo/:state/:search_term + */ + public GitHubIssues searchIssues(final String user, final String repo, + final String state, final String searchTerm) + throws GitHubServiceException { + GitHubIssues issues = null; + GetMethod method = null; + try { + // build HTTP GET method + if (searchTerm.trim().length() == 0) { // no search term: list all + method = new GetMethod(gitURLBase + gitIssueRoot + LIST + user + + "/" + repo + "/" + state); + } else { + method = new GetMethod(gitURLBase + gitIssueRoot + SEARCH + + user + "/" + repo + "/" + state + "/" + searchTerm); + } + // execute HTTP GET method + executeMethod(method); + // transform JSON to Java object + issues = gson.fromJson(new String(method.getResponseBody()), + GitHubIssues.class); + } catch (GitHubServiceException e) { + throw e; + } catch (final RuntimeException runtimeException) { + throw runtimeException; + } catch (final Exception exception) { + throw new GitHubServiceException(exception); + } finally { + if (method != null) + method.releaseConnection(); + } + return issues; + } + + /** + * Add a label to an existing GitHub issue. + * + * @param user + * - The user the repository is owned by + * @param repo + * - The git repository where the issue tracker is hosted + * @param label + * - The text label to add to the existing issue + * @param issueNumber + * - The issue number to add a label to + * @param api + * - The users GitHub + * + * @return A boolean representing the success of the function call + * + * @throws GitHubServiceException + * + * @note API Doc: issues/label/add/:user/:repo/:label/:number API POST + * Variables: login, api-token + */ + public boolean addLabel(final String user, final String repo, + final String label, final int issueNumber,final GitHubCredentials credentials) + throws GitHubServiceException { + PostMethod method = null; + + boolean success = false; + + try { + // build HTTP GET method + method = new PostMethod(gitURLBase + gitIssueRoot + ADD_LABEL + + user + "/" + repo + "/" + label + "/" + + Integer.toString(issueNumber)); + + // Set the users login and API token + final NameValuePair login = new NameValuePair("login", credentials.getUsername()); + final NameValuePair token = new NameValuePair("token", credentials.getApiToken()); + method.setRequestBody(new NameValuePair[] { login, token }); + + // execute HTTP GET method + executeMethod(method); + // Check the response, make sure the action was successful + final String response = method.getResponseBodyAsString(); + if (response.contains(label.subSequence(0, label.length()))) { + success = true; + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Response: " + method.getResponseBodyAsString()); + LOG.debug("URL: " + method.getURI()); + } + } catch (GitHubServiceException e) { + throw e; + } catch (final RuntimeException runtimeException) { + throw runtimeException; + } catch (final Exception exception) { + throw new GitHubServiceException(exception); + } finally { + if (method != null) + method.releaseConnection(); + } + return success; + } + + /** + * Remove an existing label from an existing GitHub issue. + * + * @param user + * - The user the repository is owned by + * @param repo + * - The git repository where the issue tracker is hosted + * @param label + * @param issueNumber + * @param api + * + * @return A list of GitHub issues in the response text. + * + * @throws GitHubServiceException + * + * API Doc: issues/label/remove/:user/:repo/:label/:number API + * POST Variables: login, api-token + */ + public boolean removeLabel(final String user, final String repo, + final String label, final int issueNumber, final GitHubCredentials credentials) + throws GitHubServiceException { + PostMethod method = null; + boolean success = false; + try { + // build HTTP GET method + method = new PostMethod(gitURLBase + gitIssueRoot + REMOVE_LABEL + + user + "/" + repo + "/" + label + "/" + + Integer.toString(issueNumber)); + + // Set the users login and API token + final NameValuePair login = new NameValuePair("login", credentials.getUsername()); + final NameValuePair token = new NameValuePair("token", credentials.getUsername()); + method.setRequestBody(new NameValuePair[] { login, token }); + + // execute HTTP GET method + executeMethod(method); + // Check the response, make sure the action was successful + final String response = method.getResponseBodyAsString(); + if (!response.contains(label.subSequence(0, label.length()))) { + success = true; + } + if (LOG.isDebugEnabled()) { + LOG.debug("Response: " + method.getResponseBodyAsString()); + LOG.debug("URL: " + method.getURI()); + } + } catch (GitHubServiceException e) { + throw e; + } catch (final RuntimeException runtimeException) { + throw runtimeException; + } catch (final Exception exception) { + throw new GitHubServiceException(exception); + } finally { + if (method != null) + method.releaseConnection(); + } + return success; + } + + /** + * Open a new issue using the GitHub Issues API. + * + * @param user + * - The user the repository is owned by + * @param repo + * - The git repository where the issue tracker is hosted + * @param issue + * - The GitHub issue object to create on the issue tracker. + * + * @return the issue that was created + * + * @throws GitHubServiceException + * + * API Doc: issues/open/:user/:repo API POST Variables: login, + * api-token, title, body + */ + public GitHubIssue openIssue(final String user, final String repo, + final GitHubIssue issue, final GitHubCredentials credentials) + throws GitHubServiceException { + + GitHubShowIssue showIssue = null; + + PostMethod method = null; + try { + // Create the HTTP POST method + method = new PostMethod(gitURLBase + gitIssueRoot + OPEN + user + + "/" + repo); + // Set the users login and API token + final NameValuePair login = new NameValuePair("login", credentials.getUsername()); + final NameValuePair token = new NameValuePair("token", credentials.getApiToken()); + final NameValuePair body = new NameValuePair("body", issue + .getBody()); + final NameValuePair title = new NameValuePair("title", issue + .getTitle()); + + method.setRequestBody(new NameValuePair[] { login, token, body, + title }); + + executeMethod(method); + showIssue = gson.fromJson(new String(method.getResponseBody()), + GitHubShowIssue.class); + + + if (showIssue == null || showIssue.getIssue() == null) { + if (LOG.isErrorEnabled()) { + LOG.error("Unexpected server response: "+method.getResponseBodyAsString()); + } + throw new GitHubServiceException("Unexpected server response"); + } + if (LOG.isDebugEnabled()) { + LOG.debug("Response: " + method.getResponseBodyAsString()); + LOG.debug("URL: " + method.getURI()); + } + return showIssue.getIssue(); + } catch (GitHubServiceException e) { + throw e; + } catch (final RuntimeException runTimeException) { + throw runTimeException; + } catch (final Exception e) { + throw new GitHubServiceException(e); + } finally { + if (method != null) { + method.releaseConnection(); + } + } + } + + /** + * Edit an existing issue using the GitHub Issues API. + * + * @param user + * - The user the repository is owned by + * @param repo + * - The git repository where the issue tracker is hosted + * @param issue + * - The GitHub issue object to create on the issue tracker. + * + * @return the issue with changes + * + * @throws GitHubServiceException + * + * API Doc: issues/edit/:user/:repo/:number API POST Variables: + * login, api-token, title, body + */ + public GitHubIssue editIssue(final String user, final String repo, + final GitHubIssue issue,final GitHubCredentials credentials) + throws GitHubServiceException { + PostMethod method = null; + try { + + // Create the HTTP POST method + method = new PostMethod(gitURLBase + gitIssueRoot + EDIT + user + + "/" + repo + "/" + issue.getNumber()); + // Set the users login and API token + final NameValuePair login = new NameValuePair("login", credentials.getUsername()); + final NameValuePair token = new NameValuePair("token", credentials.getApiToken()); + final NameValuePair body = new NameValuePair("body", issue + .getBody()); + final NameValuePair title = new NameValuePair("title", issue + .getTitle()); + + method.setRequestBody(new NameValuePair[] { login, token, body, + title }); + + executeMethod(method); + GitHubShowIssue showIssue = gson.fromJson(method.getResponseBodyAsString(), + GitHubShowIssue.class); + + // Make sure the changes were made properly + if (showIssue == null || showIssue.getIssue() == null) { + if (LOG.isErrorEnabled()) { + LOG.error("Unexpected server response: "+method.getResponseBodyAsString()); + } + throw new GitHubServiceException("Unexpected server response"); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Response: " + method.getResponseBodyAsString()); + LOG.debug("URL: " + method.getURI()); + } + return showIssue.getIssue(); + } catch (final RuntimeException runTimeException) { + throw runTimeException; + } catch (final Exception e) { + throw new GitHubServiceException(e); + } finally { + if (method != null) { + method.releaseConnection(); + } + } + } + + + public GitHubIssue showIssue(final String user, final String repo,final String issueNumber) throws GitHubServiceException { + GetMethod method = null; + try { + // build HTTP GET method + method = new GetMethod(gitURLBase + gitIssueRoot + SHOW + + user + "/" + repo + "/" + issueNumber); + + // execute HTTP GET method + executeMethod(method); + // transform JSON to Java object + GitHubShowIssue issue = gson.fromJson(new String(method.getResponseBody()), + GitHubShowIssue.class); + + return issue.getIssue(); + } catch (GitHubServiceException e) { + throw e; + } catch (final RuntimeException runtimeException) { + throw runtimeException; + } catch (final Exception exception) { + throw new GitHubServiceException(exception); + } finally { + if (method != null) { + method.releaseConnection(); + } + } + } + + private void executeMethod(HttpMethod method) throws GitHubServiceException { + int status; + try { + status = httpClient.executeMethod(method); + } catch (HttpException e) { + throw new GitHubServiceException(e); + } catch (IOException e) { + throw new GitHubServiceException(e); + } + if (status != HttpStatus.SC_OK) { + switch (status) { + case HttpStatus.SC_UNAUTHORIZED: + case HttpStatus.SC_FORBIDDEN: + throw new PermissionDeniedException(method.getStatusLine()); + default: + throw new GitHubServiceException(method.getStatusLine()); + } + } + } + + /** + * Edit an existing issue using the GitHub Issues API and change its status to open. + * + * @param user + * - The user the repository is owned by + * @param repo + * - The git repository where the issue tracker is hosted + * @param issue + * - The GitHub issue object to create on the issue tracker. + * + * @return the issue with changes + * + * @throws GitHubServiceException + * + * API Doc: issues/reopen/:user/:repo/:number API POST Variables: + * login, api-token, title, body + */ + public GitHubIssue reopenIssue(String user, String repo, GitHubIssue issue, + GitHubCredentials credentials) throws GitHubServiceException { + issue = editIssue(user, repo, issue, credentials); + return changeIssueStatus(user, repo, REOPEN, issue, credentials); + } + + /** + * Edit an existing issue using the GitHub Issues API and change its status to closed. + * + * @param user + * - The user the repository is owned by + * @param repo + * - The git repository where the issue tracker is hosted + * @param issue + * - The GitHub issue object to create on the issue tracker. + * + * @return the issue with changes + * + * @throws GitHubServiceException + * + * API Doc: issues/close/:user/:repo/:number API POST Variables: + * login, api-token, title, body + */ + public GitHubIssue closeIssue(String user, String repo, GitHubIssue issue, + GitHubCredentials credentials) throws GitHubServiceException { + issue = editIssue(user, repo, issue, credentials); + return changeIssueStatus(user, repo, CLOSE, issue, credentials); + + } + + private GitHubIssue changeIssueStatus(final String user, final String repo, + String githubOperation, final GitHubIssue issue, + final GitHubCredentials credentials) throws GitHubServiceException { + PostMethod method = null; + try { + + // Create the HTTP POST method + method = new PostMethod(gitURLBase + gitIssueRoot + githubOperation + user + + "/" + repo + "/" + issue.getNumber()); + // Set the users login and API token + final NameValuePair login = new NameValuePair("login", credentials.getUsername()); + final NameValuePair token = new NameValuePair("token", credentials.getApiToken()); + + method.setRequestBody(new NameValuePair[] { login, token }); + + executeMethod(method); + GitHubShowIssue showIssue = gson.fromJson(method.getResponseBodyAsString(), + GitHubShowIssue.class); + + // Make sure the changes were made properly + if (showIssue == null || showIssue.getIssue() == null) { + if (LOG.isErrorEnabled()) { + LOG.error("Unexpected server response: "+method.getResponseBodyAsString()); + } + throw new GitHubServiceException("Unexpected server response"); + } + + if (LOG.isDebugEnabled()) { + LOG.debug("Response: " + method.getResponseBodyAsString()); + LOG.debug("URL: " + method.getURI()); + } + return showIssue.getIssue(); + } catch (final RuntimeException runTimeException) { + throw runTimeException; + } catch (final Exception e) { + throw new GitHubServiceException(e); + } finally { + if (method != null) { + method.releaseConnection(); + } + } + } +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubServiceException.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubServiceException.java new file mode 100644 index 00000000..3e9a51c2 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubServiceException.java @@ -0,0 +1,58 @@ +/******************************************************************************* + * 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 - initial contribution + * Christian Trutz - initial contribution + * Chris Aniszczyk - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.github.internal; + +import org.apache.commons.httpclient.StatusLine; + +/** + * Exception generated by the GitHubService + */ +public class GitHubServiceException extends Exception { + + /** + * Auto generated serialVersionUID + */ + private static final long serialVersionUID = -6287902058352190022L; + + private int httpStatusCode = Integer.MIN_VALUE; + + /** + * Constructor for the GitHubServiceException + * + * @param exception + * - Exception to wrap around + */ + protected GitHubServiceException(final Exception exception) { + super(exception); + } + + protected GitHubServiceException(String message, Throwable cause) { + super(message, cause); + } + + protected GitHubServiceException(String message) { + super(message); + } + + protected GitHubServiceException(StatusLine statusLine) { + this(String.format("HTTP %s: %s",statusLine.getStatusCode(),statusLine.getReasonPhrase())); + httpStatusCode = statusLine.getStatusCode(); + } + + /** + * the HTTP status code, or -1 if unknown or not applicable. + */ + public int getHttpStatusCode() { + return httpStatusCode; + } +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubShowIssue.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubShowIssue.java new file mode 100644 index 00000000..f249c53f --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubShowIssue.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * 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 - initial contribution + * Christian Trutz - initial contribution + * Chris Aniszczyk - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.github.internal; + +public class GitHubShowIssue { + private GitHubIssue issue; + + public GitHubIssue getIssue() { + return issue; + } + + public void setIssue(GitHubIssue issue) { + this.issue = issue; + } + +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskAttributeMapper.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskAttributeMapper.java new file mode 100644 index 00000000..2d42de8d --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskAttributeMapper.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * 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 - initial contribution + * Christian Trutz - initial contribution + * Chris Aniszczyk - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.github.internal; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper; + +public class GitHubTaskAttributeMapper extends TaskAttributeMapper { + + private DateFormat dateFormat = SimpleDateFormat.getDateTimeInstance(); + + public GitHubTaskAttributeMapper(TaskRepository taskRepository) { + super(taskRepository); + } + + @Override + public String mapToRepositoryKey(TaskAttribute parent, String key) { + return key; + } + + @Override + public Date getDateValue(TaskAttribute attribute) { + String value = attribute.getValue(); + if (value != null) { + try { + return dateFormat.parse(value); + } catch (ParseException e) { + return super.getDateValue(attribute); + } + } + return null; + } +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskAttributes.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskAttributes.java new file mode 100644 index 00000000..b43694d0 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskAttributes.java @@ -0,0 +1,71 @@ +/******************************************************************************* + * 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 - initial contribution + * Christian Trutz - initial contribution + * Chris Aniszczyk - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.github.internal; + +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; + +public enum GitHubTaskAttributes { + + KEY("Key",TaskAttribute.TASK_KEY,TaskAttribute.TYPE_SHORT_TEXT,true,true,true), + TITLE("Summary",TaskAttribute.SUMMARY,TaskAttribute.TYPE_SHORT_TEXT,true,false,true), + BODY("Description",TaskAttribute.DESCRIPTION,TaskAttribute.TYPE_LONG_RICH_TEXT,true,false,true), + + CREATION_DATE("Created",TaskAttribute.DATE_CREATION,TaskAttribute.TYPE_DATETIME,true,true,false), + MODIFICATION_DATE("Modified",TaskAttribute.DATE_MODIFICATION,TaskAttribute.TYPE_DATETIME,true,true,false), + CLOSED_DATE("Closed",TaskAttribute.DATE_COMPLETION,TaskAttribute.TYPE_DATETIME,false,true,false), + + STATUS("Status",TaskAttribute.STATUS,TaskAttribute.TYPE_SHORT_TEXT,true,false,true) + ; + + + private final String id; + private final String label; + private final boolean readOnly; + private final boolean initTask; + private final boolean requiredForFullTaskData; + private final String type; + + private GitHubTaskAttributes(String label, String id,String type,boolean requiredForFullTaskData, boolean readOnly, boolean initTask) { + this.label = label; + this.requiredForFullTaskData = requiredForFullTaskData; + this.id = id==null?"github."+name():id; + this.type = type; + this.readOnly = readOnly; + this.initTask = initTask; + } + + public String getLabel() { + return label; + } + public String getId() { + return id; + } + public String getType() { + return type; + } + public boolean isReadOnly() { + return readOnly; + } + + public String getKind() { + return TaskAttribute.KIND_DEFAULT; + } + + public boolean isInitTask() { + return initTask; + } + + public boolean isRequiredForFullTaskData() { + return requiredForFullTaskData; + } +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskDataHandler.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskDataHandler.java new file mode 100644 index 00000000..62649e79 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskDataHandler.java @@ -0,0 +1,265 @@ +/******************************************************************************* + * 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 - initial contribution + * Christian Trutz - initial contribution + * Chris Aniszczyk - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.github.internal; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Set; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.mylyn.tasks.core.ITaskMapping; +import org.eclipse.mylyn.tasks.core.RepositoryResponse; +import org.eclipse.mylyn.tasks.core.TaskRepository; +import org.eclipse.mylyn.tasks.core.RepositoryResponse.ResponseKind; +import org.eclipse.mylyn.tasks.core.data.AbstractTaskDataHandler; +import org.eclipse.mylyn.tasks.core.data.TaskAttribute; +import org.eclipse.mylyn.tasks.core.data.TaskAttributeMapper; +import org.eclipse.mylyn.tasks.core.data.TaskAttributeMetaData; +import org.eclipse.mylyn.tasks.core.data.TaskData; +import org.eclipse.mylyn.tasks.core.data.TaskOperation; + +public class GitHubTaskDataHandler extends AbstractTaskDataHandler { + + private static final String DATA_VERSION = "1"; + /** + * + */ + private GitHubTaskAttributeMapper taskAttributeMapper = null; + private final GitHubRepositoryConnector connector; + private DateFormat dateFormat = SimpleDateFormat.getDateTimeInstance(); + + private DateFormat githubDateFormat = new SimpleDateFormat("yyyy/mm/dd HH:MM:ss Z"); + + public GitHubTaskDataHandler(GitHubRepositoryConnector connector) { + this.connector = connector; + } + + @Override + public TaskAttributeMapper getAttributeMapper(TaskRepository taskRepository) { + if (this.taskAttributeMapper == null) + this.taskAttributeMapper = new GitHubTaskAttributeMapper( + taskRepository); + return this.taskAttributeMapper; + } + + public TaskData createPartialTaskData(TaskRepository repository, + IProgressMonitor monitor,String user, String project, GitHubIssue issue) { + + TaskData data = new TaskData(getAttributeMapper(repository), + GitHubRepositoryConnector.KIND, repository.getRepositoryUrl(), + issue.getNumber()); + data.setVersion(DATA_VERSION); + + createOperations(data,issue); + + + createAttribute(data, GitHubTaskAttributes.KEY,issue.getNumber()); + createAttribute(data, GitHubTaskAttributes.TITLE, issue.getTitle()); + createAttribute(data, GitHubTaskAttributes.BODY, issue.getBody()); + createAttribute(data, GitHubTaskAttributes.STATUS, issue.getState()); + createAttribute(data, GitHubTaskAttributes.CREATION_DATE, toLocalDate(issue.getCreated_at())); + createAttribute(data, GitHubTaskAttributes.MODIFICATION_DATE, toLocalDate(issue.getCreated_at())); + createAttribute(data, GitHubTaskAttributes.CLOSED_DATE, toLocalDate(issue.getClosed_at())); + + if (isPartial(data)) { + data.setPartial(true); + } + + return data; + } + + + private boolean isPartial(TaskData data) { + for (GitHubTaskAttributes attribute: GitHubTaskAttributes.values()) { + if (attribute.isRequiredForFullTaskData()) { + TaskAttribute taskAttribute = data.getRoot().getAttribute(attribute.getId()); + if (taskAttribute == null) { + return true; + } + } + } + return false; + } + + private void createOperations(TaskData data, GitHubIssue issue) { + TaskAttribute operationAttribute = data.getRoot().createAttribute(TaskAttribute.OPERATION); + operationAttribute.getMetaData().setType(TaskAttribute.TYPE_OPERATION); + + if (!data.isNew()) { + if (issue.getState() != null) { + addOperation(data,issue,GitHubTaskOperation.LEAVE,true); + if (issue.getState().equals("open")) { + addOperation(data,issue,GitHubTaskOperation.CLOSE,false); + } else if (issue.getState().equals("closed")) { + addOperation(data,issue,GitHubTaskOperation.REOPEN,false); + } + } + } + } + + private void addOperation(TaskData data, GitHubIssue issue, GitHubTaskOperation operation,boolean asDefault) { + TaskAttribute attribute = data.getRoot().createAttribute(TaskAttribute.PREFIX_OPERATION + operation.getId()); + String label = createOperationLabel(issue, operation); + TaskOperation.applyTo(attribute, operation.getId(), label); + + if (asDefault) { + TaskAttribute operationAttribute = data.getRoot().getAttribute(TaskAttribute.OPERATION); + TaskOperation.applyTo(operationAttribute, operation.getId(), label); + } + } + + private String createOperationLabel(GitHubIssue issue, + GitHubTaskOperation operation) { + return operation==GitHubTaskOperation.LEAVE?operation.getLabel()+issue.getState():operation.getLabel(); + } + + private String toLocalDate(String date) { + if (date != null && date.trim().length() > 0) { + // expect "2010/02/02 22:58:39 -0800" + try { + Date d = githubDateFormat.parse(date); + date = dateFormat.format(d); + } catch (ParseException e) { + // ignore + } + } + return date; + } + + private String toGitHubDate(TaskData taskData, + GitHubTaskAttributes attr) { + TaskAttribute attribute = taskData.getRoot().getAttribute(attr.name()); + String value = attribute==null?null:attribute.getValue(); + if (value != null) { + try { + Date d = dateFormat.parse(value); + value = githubDateFormat.format(d); + } catch (ParseException e) { + // ignore + } + } + return value; + } + + public TaskData createTaskData(TaskRepository repository, + IProgressMonitor monitor, String user, String project, + GitHubIssue issue) { + TaskData taskData = createPartialTaskData(repository, monitor, user, project, issue); + taskData.setPartial(false); + + return taskData; + } + + private GitHubIssue createIssue(TaskData taskData) { + GitHubIssue issue = new GitHubIssue(); + if (!taskData.isNew()) { + issue.setNumber(taskData.getTaskId()); + } + issue.setBody(getAttributeValue(taskData,GitHubTaskAttributes.BODY)); + issue.setTitle(getAttributeValue(taskData,GitHubTaskAttributes.TITLE)); + issue.setState(getAttributeValue(taskData,GitHubTaskAttributes.STATUS)); + issue.setCreated_at(toGitHubDate(taskData,GitHubTaskAttributes.CREATION_DATE)); + issue.setCreated_at(toGitHubDate(taskData,GitHubTaskAttributes.MODIFICATION_DATE)); + issue.setCreated_at(toGitHubDate(taskData,GitHubTaskAttributes.CLOSED_DATE)); + return issue; + } + + private String getAttributeValue(TaskData taskData, + GitHubTaskAttributes attr) { + TaskAttribute attribute = taskData.getRoot().getAttribute(attr.getId()); + return attribute==null?null:attribute.getValue(); + } + + private void createAttribute(TaskData data, GitHubTaskAttributes attribute, String value) { + TaskAttribute attr = data.getRoot().createAttribute(attribute.getId()); + TaskAttributeMetaData metaData = attr.getMetaData(); + metaData.defaults() + .setType(attribute.getType()) + .setKind(attribute.getKind()) + .setLabel(attribute.getLabel()) + .setReadOnly(attribute.isReadOnly()); + + if (value != null) { + attr.addValue(value); + } + } + + @Override + public boolean initializeTaskData(TaskRepository repository, TaskData data, + ITaskMapping initializationData, IProgressMonitor monitor) + throws CoreException { + + data.setVersion(DATA_VERSION); + + for (GitHubTaskAttributes attr: GitHubTaskAttributes.values()) { + if (attr.isInitTask()) { + createAttribute(data, attr,null); + } + } + + return true; + } + + @Override + public RepositoryResponse postTaskData(TaskRepository repository, + TaskData taskData, Set oldAttributes, + IProgressMonitor monitor) throws CoreException { + + GitHubIssue issue = createIssue(taskData); + String user = GitHub.computeTaskRepositoryUser(repository.getUrl()); + String repo = GitHub.computeTaskRepositoryProject(repository.getUrl()); + try { + + GitHubService service = connector.getService(); + GitHubCredentials credentials = GitHubCredentials.create(repository); + if (taskData.isNew()) { + issue = service.openIssue(user , repo, issue, credentials); + } else { + TaskAttribute operationAttribute = taskData.getRoot().getAttribute(TaskAttribute.OPERATION); + + GitHubTaskOperation operation = null; + + + if (operationAttribute != null) { + String opId = operationAttribute.getValue(); + operation = GitHubTaskOperation.fromId(opId); + + } + if (operation != null && operation != GitHubTaskOperation.LEAVE) { + service.editIssue(user , repo, issue, credentials); + switch (operation) { + case REOPEN: + service.reopenIssue(user,repo,issue,credentials); + break; + case CLOSE: + service.closeIssue(user,repo,issue,credentials); + break; + default: + throw new IllegalStateException("not implemented: "+operation); + } + } else { + service.editIssue(user , repo, issue, credentials); + } + } + return new RepositoryResponse(taskData.isNew()?ResponseKind.TASK_CREATED:ResponseKind.TASK_UPDATED,issue.getNumber()); + } catch (GitHubServiceException e) { + throw new CoreException(GitHub.createErrorStatus(e)); + } + + } + + +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskOperation.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskOperation.java new file mode 100644 index 00000000..7043d477 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/GitHubTaskOperation.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * 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 - initial contribution + * Christian Trutz - initial contribution + * Chris Aniszczyk - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.github.internal; + +public enum GitHubTaskOperation { + LEAVE("Leave as "), + REOPEN("Reopen"), + CLOSE("Close"); + + private final String label; + + private GitHubTaskOperation(String label) { + this.label = label; + } + + public String getLabel() { + return label; + } + + public String getId() { + return name(); + } + + /** + * get the operation by its id + * @param opId the id, or null + * @return the operation, or null if the id was null or did not match any operation + */ + public static GitHubTaskOperation fromId(String opId) { + for (GitHubTaskOperation op: values()) { + if (op.getId().equals(opId)) { + return op; + } + } + return null; + } + +} diff --git a/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/PermissionDeniedException.java b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/PermissionDeniedException.java new file mode 100644 index 00000000..f2401048 --- /dev/null +++ b/org.eclipse.mylyn.github.core/src/org/eclipse/mylyn/github/internal/PermissionDeniedException.java @@ -0,0 +1,37 @@ +/******************************************************************************* + * 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 - initial contribution + * Christian Trutz - initial contribution + * Chris Aniszczyk - initial contribution + *******************************************************************************/ +package org.eclipse.mylyn.github.internal; + +import org.apache.commons.httpclient.StatusLine; + +public class PermissionDeniedException extends GitHubServiceException { + + private static final long serialVersionUID = -4635370712942848361L; + + protected PermissionDeniedException(Exception exception) { + super(exception); + } + + protected PermissionDeniedException(StatusLine statusLine) { + super(statusLine); + } + + protected PermissionDeniedException(String message, Throwable cause) { + super(message, cause); + } + + protected PermissionDeniedException(String message) { + super(message); + } + +} -- cgit v1.2.3