Package com.urswolfer.intellij.plugin.gerrit.rest

Source Code of com.urswolfer.intellij.plugin.gerrit.rest.GerritUtil

/*
* 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.Strings;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.gerrit.extensions.api.GerritApi;
import com.google.gerrit.extensions.api.changes.*;
import com.google.gerrit.extensions.common.*;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.inject.Inject;
import com.intellij.idea.ActionsBundle;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.openapi.application.ApplicationManager;
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.util.Consumer;
import com.urswolfer.gerrit.client.rest.GerritAuthData;
import com.urswolfer.gerrit.client.rest.GerritRestApiFactory;
import com.urswolfer.gerrit.client.rest.http.HttpStatusException;
import com.urswolfer.intellij.plugin.gerrit.GerritSettings;
import com.urswolfer.intellij.plugin.gerrit.SelectedRevisions;
import com.urswolfer.intellij.plugin.gerrit.ui.LoginDialog;
import com.urswolfer.intellij.plugin.gerrit.util.NotificationBuilder;
import com.urswolfer.intellij.plugin.gerrit.util.NotificationService;
import com.urswolfer.intellij.plugin.gerrit.util.UrlUtils;
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 javax.swing.event.HyperlinkEvent;
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 {

    @Inject
    private GerritSettings gerritSettings;
    @Inject
    private SslSupport sslSupport;
    @Inject
    private Logger log;
    @Inject
    private NotificationService notificationService;
    @Inject
    private GerritApi gerritClient;
    @Inject
    private GerritRestApiFactory gerritRestApiFactory;
    @Inject
    private ProxyHttpClientBuilderExtension proxyHttpClientBuilderExtension;
    @Inject
    private SelectedRevisions selectedRevisions;

    public <T> T accessToGerritWithModalProgress(Project project,
                                                 ThrowableComputable<T, Exception> computable) {
        return accessToGerritWithModalProgress(project, computable, gerritSettings);
    }

    public <T> T accessToGerritWithModalProgress(Project project,
                                                 ThrowableComputable<T, Exception> computable,
                                                 GerritAuthData gerritAuthData) {
        try {
            return doAccessToGerritWithModalProgress(project, computable);
        } catch (Exception e) {
            if (sslSupport.isCertificateException(e)) {
                if (sslSupport.askIfShouldProceed(gerritAuthData.getHost())) {
                    // retry with the host being already trusted
                    return doAccessToGerritWithModalProgress(project, computable);
                } else {
                    return null;
                }
            }
            throw Throwables.propagate(e);
        }
    }

    private <T> T doAccessToGerritWithModalProgress(final Project project,
                                                    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 void postReview(final String changeId,
                           final String revision,
                           final ReviewInput reviewInput,
                           final Project project,
                           final Consumer<Void> consumer) {
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    gerritClient.changes().id(changeId).revision(revision).review(reviewInput);
                    return null;
                } catch (RestApiException e) {
                    throw Throwables.propagate(e);
                }
            }
        };
        accessGerrit(supplier, consumer, project, "Failed to post Gerrit review.");
    }

    public void postSubmit(final String changeId,
                           final SubmitInput submitInput,
                           final Project project,
                           final Consumer<Void> consumer) {
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    gerritClient.changes().id(changeId).current().submit(submitInput);
                    return null;
                } catch (RestApiException e) {
                    throw Throwables.propagate(e);
                }
            }
        };
        accessGerrit(supplier, consumer, project, "Failed to submit Gerrit change.");
    }

    public void postAbandon(final String changeId,
                            final AbandonInput abandonInput,
                            final Project project) {
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    gerritClient.changes().id(changeId).abandon(abandonInput);
                    return null;
                } catch (RestApiException e) {
                    throw Throwables.propagate(e);
                }
            }
        };
        accessGerrit(supplier, Consumer.EMPTY_CONSUMER, project, "Failed to abandon Gerrit change.");
    }

    /**
     * Star-endpoint added in Gerrit 2.8.
     */
    public void changeStarredStatus(final String id,
                                    final boolean starred,
                                    final Project project) {
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    if (starred) {
                        gerritClient.accounts().self().starChange(id);
                    } else {
                        gerritClient.accounts().self().unstarChange(id);
                    }
                    return null;
                } catch (RestApiException e) {
                    throw Throwables.propagate(e);
                }
            }
        };
        accessGerrit(supplier, Consumer.EMPTY_CONSUMER, project, "Failed to star Gerrit change." +
                "<br/>Not supported for Gerrit instances older than version 2.8.");
    }

    public void setReviewed(final String changeId,
                            final String revision,
                            final String filePath,
                            final Project project) {
        if (!gerritSettings.isLoginAndPasswordAvailable()) {
            return;
        }
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    gerritClient.changes().id(changeId).revision(revision).setReviewed(filePath, true);
                    return null;
                } catch (RestApiException e) {
                    throw Throwables.propagate(e);
                }
            }
        };
        accessGerrit(supplier, Consumer.EMPTY_CONSUMER, project, "Failed set file review status for Gerrit change.");
    }

    public void getChangesToReview(Project project, Consumer<List<ChangeInfo>> consumer) {
        Changes.QueryRequest queryRequest = gerritClient.changes().query("is:open+reviewer:self");
        getChanges(queryRequest, project, consumer);
    }

    public void getChangesForProject(String query, final Project project, final Consumer<LoadChangesProxy> consumer) {
        if (!gerritSettings.getListAllChanges()) {
            query = appendQueryStringForProject(project, query);
        }
        getChanges(query, project, consumer);
    }

    public void getChanges(final String query, final Project project, final Consumer<LoadChangesProxy> consumer) {
        Supplier<LoadChangesProxy> supplier = new Supplier<LoadChangesProxy>() {
            @Override
            public LoadChangesProxy get() {
                    Changes.QueryRequest queryRequest = gerritClient.changes().query(query)
                            .withOptions(EnumSet.of(ListChangesOption.ALL_REVISIONS, ListChangesOption.LABELS));
                    return new LoadChangesProxy(queryRequest, GerritUtil.this, project);
            }
        };
        accessGerrit(supplier, consumer, project);
    }

    public void getChanges(final Changes.QueryRequest queryRequest, final Project project, Consumer<List<ChangeInfo>> consumer) {
        Supplier<List<ChangeInfo>> supplier = new Supplier<List<ChangeInfo>>() {
            @Override
            public List<ChangeInfo> get() {
                try {
                    return queryRequest.get();
                } catch (RestApiException e) {
                    notifyError(e, "Failed to get Gerrit changes.", project);
                    return Collections.emptyList();
                }
            }
        };
        accessGerrit(supplier, consumer, project);
    }

    private String appendQueryStringForProject(Project project, String query) {
        String projectQueryPart = getProjectQueryPart(project);
        query = Joiner.on('+').skipNulls().join(Strings.emptyToNull(query), Strings.emptyToNull(projectQueryPart));
        return query;
    }

    private String getProjectQueryPart(Project project) {
        List<GitRepository> repositories = GitUtil.getRepositoryManager(project).getRepositories();
        if (repositories.isEmpty()) {
            showAddGitRepositoryNotification(project);
            return "";
        }

        List<GitRemote> remotes = Lists.newArrayList();
        for (GitRepository repository : repositories) {
            remotes.addAll(repository.getRemotes());
        }

        List<String> projectNames = Lists.newArrayList();
        for (GitRemote remote : remotes) {
            for (String remoteUrl : remote.getUrls()) {
                remoteUrl = UrlUtils.stripGitExtension(remoteUrl);
                String projectName = getProjectName(gerritSettings.getHost(), remoteUrl);
                if (!Strings.isNullOrEmpty(projectName) && remoteUrl.endsWith(projectName)) {
                    projectNames.add("project:" + projectName);
                }
            }
        }

        if (projectNames.isEmpty()) {
            return "";
        }
        return String.format("(%s)", Joiner.on("+OR+").join(projectNames));
    }

    private String getProjectName(String repositoryUrl, String url) {
        if (!repositoryUrl.endsWith("/")) {
            repositoryUrl = repositoryUrl + "/";
        }

        String basePath = UrlUtils.createUriFromGitConfigString(repositoryUrl).getPath();
        String path = UrlUtils.createUriFromGitConfigString(url).getPath();

        if (path.length() >= basePath.length()) {
            path = path.substring(basePath.length());
        }

        path = UrlUtils.stripGitExtension(path);

        if (path.endsWith("/")) {
            path = path.substring(0, path.length() - 1);
        }

        return path;
    }

    public void showAddGitRepositoryNotification(final Project project) {
        NotificationBuilder notification = new NotificationBuilder(project, "Insufficient dependencies for Gerrit plugin",
                "Please configure a Git repository.<br/><a href='vcs'>Open Settings</a>")
                .listener(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"));
                            }
                        }
                    }
                });
        notificationService.notifyWarning(notification);
    }

    public void getChangeDetails(final int changeNr, final Project project, final Consumer<ChangeInfo> consumer) {
        Supplier<ChangeInfo> supplier = new Supplier<ChangeInfo>() {
            @Override
            public ChangeInfo get() {
                try {
                    EnumSet<ListChangesOption> options = EnumSet.of(
                            ListChangesOption.ALL_REVISIONS,
                            ListChangesOption.MESSAGES,
                            ListChangesOption.LABELS,
                            ListChangesOption.DETAILED_LABELS);
                    try {
                        return gerritClient.changes().id(changeNr).get(options);
                    } catch (HttpStatusException e) {
                        // remove special handling (-> just notify error) once we drop Gerrit < 2.7 support
                        if (e.getStatusCode() == 400) {
                            options.remove(ListChangesOption.MESSAGES);
                            return gerritClient.changes().id(changeNr).get(options);
                        } else {
                            throw e;
                        }
                    }
                } catch (RestApiException e) {
                    notifyError(e, "Failed to get Gerrit change.", project);
                    return new ChangeInfo();
                }
            }
        };
        accessGerrit(supplier, consumer, project);
    }

    /**
     * Support starting from Gerrit 2.7.
     */
    public void getComments(final String changeId,
                            final String revision,
                            final Project project,
                            final boolean includePublishedComments,
                            final boolean includeDraftComments,
                            final Consumer<Map<String, List<CommentInfo>>> consumer) {

        Supplier<Map<String, List<CommentInfo>>> supplier = new Supplier<Map<String, List<CommentInfo>>>() {
            @Override
            public Map<String, List<CommentInfo>> get() {
                try {
                    Map<String, List<CommentInfo>> comments;
                    if (includePublishedComments) {
                        comments = gerritClient.changes().id(changeId).revision(revision).comments();
                    } else {
                        comments = Maps.newHashMap();
                    }

                    Map<String, List<CommentInfo>> drafts;
                    if (includeDraftComments && gerritSettings.isLoginAndPasswordAvailable()) {
                        drafts = gerritClient.changes().id(changeId).revision(revision).drafts();
                    } else {
                        drafts = Maps.newHashMap();
                    }

                    HashMap<String, List<CommentInfo>> allComments = new HashMap<String, List<CommentInfo>>(drafts);
                    for (Map.Entry<String, List<CommentInfo>> entry : comments.entrySet()) {
                        List<CommentInfo> commentInfos = allComments.get(entry.getKey());
                        if (commentInfos != null) {
                            commentInfos.addAll(entry.getValue());
                        } else {
                            allComments.put(entry.getKey(), entry.getValue());
                        }
                    }
                    return allComments;
                } catch (RestApiException e) {
                    // remove check once we drop Gerrit < 2.7 support and fail in any case
                    if (!(e instanceof HttpStatusException) || ((HttpStatusException) e).getStatusCode() != 404) {
                        notifyError(e, "Failed to get Gerrit comments.", project);
                    }
                    return new TreeMap<String, List<CommentInfo>>();
                }
            }
        };
        accessGerrit(supplier, consumer, project);
    }

    public void saveDraftComment(final int changeNr,
                                 final String revision,
                                 final DraftInput draftInput,
                                 final Project project,
                                 final Consumer<CommentInfo> consumer) {
        Supplier<CommentInfo> supplier = new Supplier<CommentInfo>() {
            @Override
            public CommentInfo get() {
                try {
                    CommentInfo commentInfo;
                    if (draftInput.id != null) {
                        commentInfo = gerritClient.changes().id(changeNr).revision(revision)
                                .draft(draftInput.id).update(draftInput);
                    } else {
                        DraftApi draftApi = gerritClient.changes().id(changeNr).revision(revision)
                                .createDraft(draftInput);
                        commentInfo = draftApi.get();
                    }
                    return commentInfo;
                } catch (RestApiException e) {
                    throw Throwables.propagate(e);
                }
            }
        };
        accessGerrit(supplier, consumer, project, "Failed to save draft comment.");
    }

    public void deleteDraftComment(final int changeNr,
                                   final String revision,
                                   final String draftCommentId,
                                   final Project project,
                                   final Consumer<Void> consumer) {
        Supplier<Void> supplier = new Supplier<Void>() {
            @Override
            public Void get() {
                try {
                    gerritClient.changes().id(changeNr).revision(revision).draft(draftCommentId).delete();
                    return null;
                } catch (RestApiException e) {
                    throw Throwables.propagate(e);
                }
            }
        };
        accessGerrit(supplier, consumer, project, "Failed to delete draft comment.");
    }

    private boolean testConnection(GerritAuthData gerritAuthData) throws RestApiException {
        // we need to test with a temporary client with probably new (unsaved) credentials
        GerritApi tempClient = createClientWithCustomAuthData(gerritAuthData);
        if (gerritAuthData.isLoginAndPasswordAvailable()) {
            AccountInfo user = tempClient.accounts().self().get();
            return user != null;
        } else {
            tempClient.changes().query().withLimit(1).get();
            return true;
        }
    }

    /**
     * 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 boolean checkCredentials(final Project project) {
        try {
            return checkCredentials(project, gerritSettings);
        } 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 boolean checkCredentials(Project project, final GerritAuthData gerritAuthData) {
        if (Strings.isNullOrEmpty(gerritAuthData.getHost())) {
            return false;
        }
        Boolean result = accessToGerritWithModalProgress(project, new ThrowableComputable<Boolean, Exception>() {
            @Override
            public Boolean compute() throws Exception {
                ProgressManager.getInstance().getProgressIndicator().setText("Trying to login to Gerrit");
                return testConnection(gerritAuthData);
            }
        }, gerritAuthData);
        return result == null ? false : result;
    }

    /**
     * Shows Gerrit login settings if credentials are wrong or empty and return the list of all projects
     */
    public List<ProjectInfo> getAvailableProjects(final Project project) {
        while (!checkCredentials(project)) {
            final LoginDialog dialog = new LoginDialog(project, gerritSettings, this, log);
            dialog.show();
            if (!dialog.isOK()) {
                return null;
            }
        }
        // Otherwise our credentials are valid and they are successfully stored in settings
        return accessToGerritWithModalProgress(project, new ThrowableComputable<List<ProjectInfo>, Exception>() {
            @Override
            public List<ProjectInfo> compute() throws Exception {
                ProgressManager.getInstance().getProgressIndicator().setText("Extracting info about available repositories");
                return gerritClient.projects().list().get();
            }
        });
    }

    public FetchInfo getFirstFetchInfo(ChangeInfo changeDetails) {
        RevisionInfo revisionInfo = changeDetails.revisions.get(selectedRevisions.get(changeDetails));
        return getFirstFetchInfo(revisionInfo);
    }

    public FetchInfo getFirstFetchInfo(RevisionInfo revisionInfo) {
        return Iterables.getFirst(revisionInfo.fetch.values(), null);
    }

    @SuppressWarnings("UnresolvedPropertyKey")
    public 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;
    }

    public String getErrorTextFromException(Throwable t) {
        String message = t.getMessage();
        if (message == null) {
            message = "(No exception message available)";
            log.error(message, t);
        }
        return message;
    }

    private <T> void accessGerrit(final Supplier<T> supplier, final Consumer<T> consumer, final Project project) {
        accessGerrit(supplier, consumer, project, null);
    }

    /**
     * @param errorMessage if the provided supplier throws an exception, this error message is displayed (if it is not null)
     *                     and the provided consumer will not be executed.
     */
    private <T> void accessGerrit(final Supplier<T> supplier,
                              final Consumer<T> consumer,
                              final Project project,
                              final String errorMessage) {
        ApplicationManager.getApplication().invokeLater(new Runnable() {
            @Override
            public void run() {
                Task.Backgroundable backgroundTask = new Task.Backgroundable(project, "Accessing Gerrit", true) {
                    public void run(@NotNull ProgressIndicator indicator) {
                        try {
                            final T result = supplier.get();
                            ApplicationManager.getApplication().invokeLater(new Runnable() {
                                @Override
                                public void run() {
                                    //noinspection unchecked
                                    consumer.consume(result);
                                }
                            });
                        } catch (RuntimeException e) {
                            if (errorMessage != null) {
                                notifyError(e, errorMessage, project);
                            } else {
                                throw e;
                            }
                        }
                    }
                };
                backgroundTask.queue();
            }
        });
    }

    private void notifyError(Throwable throwable, String errorMessage, Project project) {
        NotificationBuilder notification = new NotificationBuilder(project, errorMessage, getErrorTextFromException(throwable));
        notificationService.notifyError(notification);
    }

    private GerritApi createClientWithCustomAuthData(GerritAuthData gerritAuthData) {
        return gerritRestApiFactory.create(gerritAuthData, sslSupport, proxyHttpClientBuilderExtension);
    }
}
TOP

Related Classes of com.urswolfer.intellij.plugin.gerrit.rest.GerritUtil

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.