Java tutorial
/* * Copyright 2000-2011 JetBrains s.r.o. * Copyright 2013 Urs Wolfer * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.urswolfer.intellij.plugin.gerrit.rest; import com.google.common.base.Joiner; import com.google.common.base.Throwables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.intellij.idea.ActionsBundle; import com.intellij.notification.Notification; import com.intellij.notification.NotificationListener; import com.intellij.notification.NotificationType; import com.intellij.notification.Notifications; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.options.ShowSettingsUtil; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.ThrowableComputable; import com.intellij.openapi.util.text.StringUtil; import com.urswolfer.intellij.plugin.gerrit.GerritSettings; import com.urswolfer.intellij.plugin.gerrit.rest.bean.*; import com.urswolfer.intellij.plugin.gerrit.ui.LoginDialog; import git4idea.GitUtil; import git4idea.config.GitVcsApplicationSettings; import git4idea.config.GitVersion; import git4idea.i18n.GitBundle; import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.event.HyperlinkEvent; import java.net.URI; import java.util.*; import java.util.concurrent.atomic.AtomicReference; /** * Parts based on org.jetbrains.plugins.github.GithubUtil * * @author Urs Wolfer * @author Konrad Dobrzynski */ public class GerritUtil { public static final Logger LOG = Logger.getInstance("gerrit"); static final String GERRIT_NOTIFICATION_GROUP = "gerrit"; @Nullable public static <T> T accessToGerritWithModalProgress(@NotNull final Project project, @NotNull String host, @NotNull final ThrowableComputable<T, Exception> computable) { try { return doAccessToGerritWithModalProgress(project, computable); } catch (Exception e) { SslSupport sslSupport = SslSupport.getInstance(); if (SslSupport.isCertificateException(e)) { if (sslSupport.askIfShouldProceed(host)) { // retry with the host being already trusted return doAccessToGerritWithModalProgress(project, computable); } else { return null; } } throw Throwables.propagate(e); } } private static <T> T doAccessToGerritWithModalProgress(@NotNull final Project project, @NotNull final ThrowableComputable<T, Exception> computable) { final AtomicReference<T> result = new AtomicReference<T>(); final AtomicReference<Exception> exception = new AtomicReference<Exception>(); ProgressManager.getInstance().run(new Task.Modal(project, "Access to Gerrit", true) { public void run(@NotNull ProgressIndicator indicator) { try { result.set(computable.compute()); } catch (Exception e) { exception.set(e); } } }); //noinspection ThrowableResultOfMethodCallIgnored if (exception.get() == null) { return result.get(); } throw Throwables.propagate(exception.get()); } public static void postReview(@NotNull String url, @NotNull String login, @NotNull String password, @NotNull String changeId, @NotNull String revision, @NotNull ReviewInput reviewInput, Project project) { final String request = "/a/changes/" + changeId + "/revisions/" + revision + "/review"; String json = new Gson().toJson(reviewInput); try { GerritApiUtil.postRequest(url, login, password, request, json); } catch (HttpStatusException e) { GerritUtil.notifyError(project, "Failed to post review.", GerritUtil.getErrorTextFromException(e)); } } public static void postSubmit(@NotNull String url, @NotNull String login, @NotNull String password, @NotNull String changeId, @NotNull SubmitInput submitInput, Project project) { final String request = "/a/changes/" + changeId + "/submit"; String json = new Gson().toJson(submitInput); try { GerritApiUtil.postRequest(url, login, password, request, json); } catch (HttpStatusException e) { GerritUtil.notifyError(project, "Failed to submit change.", GerritUtil.getErrorTextFromException(e)); } } @NotNull public static List<ChangeInfo> getChanges(@NotNull String url, @NotNull String login, @NotNull String password) { return getChanges(url, login, password, ""); } @NotNull public static List<ChangeInfo> getChangesToReview(@NotNull String url, @NotNull String login, @NotNull String password) { return getChanges(url, login, password, "?q=is:open+reviewer:self"); } /** * Provide information only for current project */ @NotNull public static List<ChangeInfo> getChangesForProject(@NotNull String url, @NotNull String login, @NotNull String password, @NotNull final Project project) { List<GitRepository> repositories = GitUtil.getRepositoryManager(project).getRepositories(); if (repositories.isEmpty()) { //Show notification showAddGitRepositoryNotification(project); return Lists.newArrayList(); } List<GitRemote> remotes = Lists.newArrayList(); for (GitRepository repository : repositories) { remotes.addAll(repository.getRemotes()); } String host = parseUri(url).getHost(); List<String> projectNames = Lists.newArrayList(); for (GitRemote remote : remotes) { for (String repositoryUrl : remote.getUrls()) { String repositoryHost = parseUri(repositoryUrl).getHost(); if (repositoryHost != null && repositoryHost.equals(host)) { projectNames.add("project:" + getProjectName(repositoryUrl)); } } } if (projectNames.isEmpty()) { return Collections.emptyList(); } String projectQuery = Joiner.on("+OR+").join(projectNames); return getChanges(url, login, password, "?q=is:open+(" + projectQuery + ')'); } private static URI parseUri(String url) { if (!url.contains("://")) { // some urls do not contain a protocol; just add something so it will not fail with parsing url = "git://" + url; } return URI.create(url); } private static String getProjectName(String url) { String path = parseUri(url).getPath(); int index = path.indexOf('/'); path = path.substring(index + 1); path = path.replace(".git", ""); // some repositories end their name with ".git" return path; } public static void showAddGitRepositoryNotification(final Project project) { Notifications.Bus.notify(new Notification(GERRIT_NOTIFICATION_GROUP, "Insufficient dependencies", "Please add git repository <br/> <a href='vcs'>Add vcs root</a>", NotificationType.WARNING, new NotificationListener() { @Override public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) { if (event.getDescription().equals("vcs")) { ShowSettingsUtil.getInstance().showSettingsDialog(project, ActionsBundle.message("group.VcsGroup.text")); } } } })); } @NotNull public static List<ChangeInfo> getChanges(@NotNull String url, @NotNull String login, @NotNull String password, @NotNull String query) { final String request = "/a/changes/" + query; JsonElement result = GerritApiUtil.getRequest(url, login, password, request); if (result == null) { return Collections.emptyList(); } return parseChangeInfos(result); } @NotNull public static ChangeInfo getChangeDetails(@NotNull String url, @NotNull String login, @NotNull String password, @NotNull String changeNr) { final String request = "/a/changes/?q=" + changeNr + "&o=CURRENT_REVISION"; JsonElement result = GerritApiUtil.getRequest(url, login, password, request); if (result == null) { throw new RuntimeException("No valid result available."); } return parseSingleChangeInfos(result.getAsJsonArray().get(0).getAsJsonObject()); } @NotNull private static List<ChangeInfo> parseChangeInfos(@NotNull JsonElement result) { if (!result.isJsonArray()) { LOG.assertTrue(result.isJsonObject(), String.format("Unexpected JSON result format: %s", result)); return Collections.singletonList(parseSingleChangeInfos(result.getAsJsonObject())); } List<ChangeInfo> changeInfoList = new ArrayList<ChangeInfo>(); for (JsonElement element : result.getAsJsonArray()) { LOG.assertTrue(element.isJsonObject(), String .format("This element should be a JsonObject: %s%nTotal JSON response: %n%s", element, result)); changeInfoList.add(parseSingleChangeInfos(element.getAsJsonObject())); } return changeInfoList; } @NotNull private static ChangeInfo parseSingleChangeInfos(@NotNull JsonObject result) { final Gson gson = createGson(); return gson.fromJson(result, ChangeInfo.class); } /** * Support starting from Gerrit 2.7. */ @NotNull public static TreeMap<String, List<CommentInfo>> getComments(@NotNull String url, @NotNull String login, @NotNull String password, @NotNull String changeId, @NotNull String revision) { final String request = "/a/changes/" + changeId + "/revisions/" + revision + "/comments/"; try { JsonElement result = GerritApiUtil.getRequest(url, login, password, request); if (result == null) { return Maps.newTreeMap(); } return parseCommentInfos(result); } catch (HttpStatusException e) { if (e.getStatusCode() == 404) { LOG.warn( "Failed to load comments; most probably because of too old Gerrit version (only 2.7 and newer supported). Returning empty."); } return Maps.newTreeMap(); } } @NotNull private static TreeMap<String, List<CommentInfo>> parseCommentInfos(@NotNull JsonElement result) { TreeMap<String, List<CommentInfo>> commentInfos = Maps.newTreeMap(); final JsonObject jsonObject = result.getAsJsonObject(); for (Map.Entry<String, JsonElement> element : jsonObject.entrySet()) { List<CommentInfo> currentCommentInfos = Lists.newArrayList(); for (JsonElement jsonElement : element.getValue().getAsJsonArray()) { currentCommentInfos.add(parseSingleCommentInfos(jsonElement.getAsJsonObject())); } commentInfos.put(element.getKey(), currentCommentInfos); } return commentInfos; } @NotNull private static CommentInfo parseSingleCommentInfos(@NotNull JsonObject result) { final Gson gson = createGson(); return gson.fromJson(result, CommentInfo.class); } private static Gson createGson() { return new GsonBuilder().setDateFormat("yyyy-MM-dd hh:mm:ss").create(); } private static boolean testConnection(final String url, final String login, final String password) { AccountInfo user = retrieveCurrentUserInfo(url, login, password); return user != null; } @Nullable private static AccountInfo retrieveCurrentUserInfo(@NotNull String url, @NotNull String login, @NotNull String password) { JsonElement result = GerritApiUtil.getRequest(url, login, password, "/a/accounts/self"); return parseUserInfo(result); } @Nullable private static AccountInfo parseUserInfo(@Nullable JsonElement result) { if (result == null) { return null; } if (!result.isJsonObject()) { LOG.error(String.format("Unexpected JSON result format: %s", result)); return null; } final Gson gson = new GsonBuilder().create(); return gson.fromJson(result, AccountInfo.class); } @NotNull private static List<ProjectInfo> getAvailableProjects(@NotNull String url, @NotNull String login, @NotNull String password) { final String request = "/a/projects/"; JsonElement result = GerritApiUtil.getRequest(url, login, password, request); if (result == null) { return Collections.emptyList(); } return parseProjectInfos(result); } @NotNull private static List<ProjectInfo> parseProjectInfos(@NotNull JsonElement result) { List<ProjectInfo> repositories = new ArrayList<ProjectInfo>(); final JsonObject jsonObject = result.getAsJsonObject(); for (Map.Entry<String, JsonElement> element : jsonObject.entrySet()) { LOG.assertTrue(element.getValue().isJsonObject(), String .format("This element should be a JsonObject: %s%nTotal JSON response: %n%s", element, result)); repositories.add(parseSingleRepositoryInfo(element.getValue().getAsJsonObject())); } return repositories; } @NotNull private static ProjectInfo parseSingleRepositoryInfo(@NotNull JsonObject result) { final Gson gson = new GsonBuilder().create(); return gson.fromJson(result, ProjectInfo.class); } /** * Checks if user has set up correct user credentials for access in the settings. * * @return true if we could successfully login with these credentials, false if authentication failed or in the case of some other error. */ public static boolean checkCredentials(final Project project) { final GerritSettings settings = GerritSettings.getInstance(); try { return checkCredentials(project, settings.getHost(), settings.getLogin(), settings.getPassword()); } catch (Exception e) { // this method is a quick-check if we've got valid user setup. // if an exception happens, we'll show the reason in the login dialog that will be shown right after checkCredentials failure. LOG.info(e); return false; } } public static boolean checkCredentials(Project project, final String url, final String login, final String password) { if (StringUtil.isEmptyOrSpaces(url) || StringUtil.isEmptyOrSpaces(login) || StringUtil.isEmptyOrSpaces(password)) { return false; } Boolean result = accessToGerritWithModalProgress(project, url, new ThrowableComputable<Boolean, Exception>() { @Override public Boolean compute() throws Exception { ProgressManager.getInstance().getProgressIndicator().setText("Trying to login to Gerrit"); return testConnection(url, login, password); } }); return result == null ? false : result; } /** * Shows Gerrit login settings if credentials are wrong or empty and return the list of all projects */ @Nullable public static List<ProjectInfo> getAvailableProjects(final Project project) { while (!checkCredentials(project)) { final LoginDialog dialog = new LoginDialog(project); dialog.show(); if (!dialog.isOK()) { return null; } } // Otherwise our credentials are valid and they are successfully stored in settings final GerritSettings settings = GerritSettings.getInstance(); final String validPassword = settings.getPassword(); return accessToGerritWithModalProgress(project, settings.getHost(), new ThrowableComputable<List<ProjectInfo>, Exception>() { @Override public List<ProjectInfo> compute() throws Exception { ProgressManager.getInstance().getProgressIndicator() .setText("Extracting info about available repositories"); return getAvailableProjects(settings.getHost(), settings.getLogin(), validPassword); } }); } public static String getRef(ChangeInfo changeDetails) { String ref = null; final TreeMap<String, RevisionInfo> revisions = changeDetails.getRevisions(); for (RevisionInfo revisionInfo : revisions.values()) { final TreeMap<String, FetchInfo> fetch = revisionInfo.getFetch(); for (FetchInfo fetchInfo : fetch.values()) { ref = fetchInfo.getRef(); } } return ref; } @SuppressWarnings("UnresolvedPropertyKey") public static boolean testGitExecutable(final Project project) { final GitVcsApplicationSettings settings = GitVcsApplicationSettings.getInstance(); final String executable = settings.getPathToGit(); final GitVersion version; try { version = GitVersion.identifyVersion(executable); } catch (Exception e) { Messages.showErrorDialog(project, e.getMessage(), GitBundle.getString("find.git.error.title")); return false; } if (!version.isSupported()) { Messages.showWarningDialog(project, GitBundle.message("find.git.unsupported.message", version.toString(), GitVersion.MIN), GitBundle.getString("find.git.success.title")); return false; } return true; } @NotNull public static String getErrorTextFromException(@NotNull Exception e) { return e.getMessage(); } public static void notifyError(@NotNull Project project, @NotNull String title, @NotNull String message) { notify(project, title, message, NotificationType.ERROR); } public static void notifyInformation(@NotNull Project project, @NotNull String title, @NotNull String message) { notify(project, title, message, NotificationType.INFORMATION); } private static void notify(@NotNull Project project, @NotNull String title, @NotNull String message, @NotNull NotificationType notificationType) { new Notification(GERRIT_NOTIFICATION_GROUP, title, message, notificationType).notify(project); } }