/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.plugin.github.ide.importer.page; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.gwt.core.client.JsArrayMixed; import com.google.gwt.regexp.shared.RegExp; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.ui.AcceptsOneWidget; import com.google.inject.Inject; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.promises.client.PromiseError; import org.eclipse.che.api.promises.client.js.Promises; import org.eclipse.che.ide.api.app.AppContext; import org.eclipse.che.ide.api.oauth.OAuth2Authenticator; import org.eclipse.che.ide.api.oauth.OAuth2AuthenticatorRegistry; import org.eclipse.che.ide.api.oauth.OAuth2AuthenticatorUrlProvider; import org.eclipse.che.ide.api.project.MutableProjectConfig; import org.eclipse.che.ide.api.wizard.AbstractWizardPage; import org.eclipse.che.ide.commons.exception.UnauthorizedException; import org.eclipse.che.ide.dto.DtoFactory; import org.eclipse.che.ide.util.NameUtils; import org.eclipse.che.plugin.github.ide.GitHubClientService; import org.eclipse.che.plugin.github.ide.GitHubLocalizationConstant; import org.eclipse.che.plugin.github.ide.load.ProjectData; import org.eclipse.che.plugin.github.shared.GitHubRepository; import org.eclipse.che.plugin.github.shared.GitHubUser; import org.eclipse.che.security.oauth.OAuthStatus; import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; /** * @author Roman Nikitenko */ public class GithubImporterPagePresenter extends AbstractWizardPage<MutableProjectConfig> implements GithubImporterPageView.ActionDelegate { // An alternative scp-like syntax: [user@]host.xz:path/to/repo.git/ private static final RegExp SCP_LIKE_SYNTAX = RegExp.compile("([A-Za-z0-9_\\-]+\\.[A-Za-z0-9_\\-:]+)+:"); // the transport protocol private static final RegExp PROTOCOL = RegExp.compile("((http|https|git|ssh|ftp|ftps)://)"); // the address of the remote server between // and / private static final RegExp HOST1 = RegExp.compile("//([A-Za-z0-9_\\-]+\\.[A-Za-z0-9_\\-:]+)+/"); // the address of the remote server between @ and : or / private static final RegExp HOST2 = RegExp.compile("@([A-Za-z0-9_\\-]+\\.[A-Za-z0-9_\\-:]+)+[:/]"); // the repository name private static final RegExp REPO_NAME = RegExp.compile("/[A-Za-z0-9_.\\-]+$"); // start with white space private static final RegExp WHITE_SPACE = RegExp.compile("^\\s"); private final DtoFactory dtoFactory; private GitHubClientService gitHubClientService; private Map<String, List<GitHubRepository>> repositories; private GitHubLocalizationConstant locale; private GithubImporterPageView view; private final String baseUrl; private final AppContext appContext; private OAuth2Authenticator gitHubAuthenticator; private boolean ignoreChanges; @Inject public GithubImporterPagePresenter(GithubImporterPageView view, OAuth2AuthenticatorRegistry gitHubAuthenticatorRegistry, GitHubClientService gitHubClientService, DtoFactory dtoFactory, AppContext appContext, GitHubLocalizationConstant locale) { this.view = view; this.baseUrl = appContext.getMasterEndpoint(); this.appContext = appContext; this.gitHubAuthenticator = gitHubAuthenticatorRegistry.getAuthenticator("github"); this.gitHubClientService = gitHubClientService; this.dtoFactory = dtoFactory; this.view.setDelegate(this); this.locale = locale; } @Override public boolean isCompleted() { return isGitUrlCorrect(dataObject.getSource().getLocation()); } @Override public void onProjectNameChanged(@NotNull String name) { if (ignoreChanges) { return; } dataObject.setName(name); updateDelegate.updateControls(); validateProjectName(); } /** * Validates project name and highlights input when error. */ private void validateProjectName() { if (NameUtils.checkProjectName(view.getProjectName())) { view.markNameValid(); } else { view.markNameInvalid(); } } @Override public void onProjectUrlChanged(@NotNull String url) { if (ignoreChanges) { return; } dataObject.getSource().setLocation(url); isGitUrlCorrect(url); String projectName = view.getProjectName(); if (projectName.isEmpty()) { projectName = extractProjectNameFromUri(url); dataObject.setName(projectName); view.setProjectName(projectName); validateProjectName(); } updateDelegate.updateControls(); } @Override public void onRecursiveSelected(boolean recursiveSelected) { if (recursiveSelected) { projectParameters().put("recursive", null); } else { projectParameters().remove("recursive"); } } @Override public void onProjectDescriptionChanged(@NotNull String projectDescription) { dataObject.setDescription(projectDescription); updateDelegate.updateControls(); } /** * Returns project parameters map. * * @return parameters map */ private Map<String, String> projectParameters() { Map<String, String> parameters = dataObject.getSource().getParameters(); if (parameters == null) { parameters = new HashMap<>(); dataObject.getSource().setParameters(parameters); } return parameters; } @Override public void onKeepDirectorySelected(boolean keepDirectory) { view.enableDirectoryNameField(keepDirectory); if (keepDirectory) { projectParameters().put("keepDir", view.getDirectoryName()); dataObject.setType("blank"); view.highlightDirectoryNameField(!NameUtils.checkProjectName(view.getDirectoryName())); view.focusDirectoryNameField(); } else { projectParameters().remove("keepDir"); dataObject.setType(null); view.highlightDirectoryNameField(false); } } @Override public void onKeepDirectoryNameChanged(@NotNull String directoryName) { if (view.keepDirectory()) { projectParameters().put("keepDir", directoryName); dataObject.setType("blank"); view.highlightDirectoryNameField(!NameUtils.checkProjectName(view.getDirectoryName())); } else { projectParameters().remove("keepDir"); dataObject.setType(null); view.highlightDirectoryNameField(false); } } @Override public void onBranchCheckBoxSelected(boolean isSelected) { view.enableBranchNameField(isSelected); if (isSelected) { projectParameters().put("branch", view.getBranchName()); view.focusBranchNameField(); } else { projectParameters().remove("branch"); } } @Override public void onBranchNameChanged(@NotNull String branchName) { if (view.isBranchCheckBoxSelected()) { projectParameters().put("branch", branchName); } else { projectParameters().remove("branch"); } } @Override public void go(@NotNull AcceptsOneWidget container) { container.setWidget(view); if (Strings.isNullOrEmpty(dataObject.getName()) && Strings.isNullOrEmpty(dataObject.getSource().getLocation())) { ignoreChanges = true; view.unmarkURL(); view.unmarkName(); view.setURLErrorMessage(null); } view.setProjectName(dataObject.getName()); view.setProjectDescription(dataObject.getDescription()); view.setProjectUrl(dataObject.getSource().getLocation()); view.setKeepDirectoryChecked(false); view.setBranchCheckBoxSelected(false); view.setDirectoryName(""); view.setBranchName(""); view.enableDirectoryNameField(false); view.enableBranchNameField(false); view.highlightDirectoryNameField(false); view.setInputsEnableState(true); view.focusInUrlInput(); ignoreChanges = false; } @Override public void onLoadRepoClicked() { getUserRepositoriesAndOrganizations(); } /** Get the list of all authorized user's repositories. */ private void getUserRepositoriesAndOrganizations() { showProcessing(true); Promise<GitHubUser> userInfo = gitHubClientService.getUserInfo(); Promise<List<GitHubUser>> organizations = gitHubClientService.getOrganizations(); Promise<List<GitHubRepository>> allRepositories = gitHubClientService.getRepositoriesList(); doRequest(userInfo, organizations, allRepositories); } protected void doRequest(Promise<GitHubUser> userInfo, Promise<List<GitHubUser>> organizations, Promise<List<GitHubRepository>> allRepositories) { Promises.all(userInfo, organizations, allRepositories).then(new Operation<JsArrayMixed>() { @Override public void apply(JsArrayMixed arg) throws OperationException { onSuccessRequest(arg); } }).catchError(new Operation<PromiseError>() { @Override public void apply(PromiseError arg) throws OperationException { onFailRequest(arg); } }); } protected void onSuccessRequest(JsArrayMixed arg) { onListLoaded(toOrgList(arg), toRepoList(arg)); showProcessing(false); } protected List<GitHubRepository> toRepoList(JsArrayMixed arg) { return dtoFactory.createListDtoFromJson(arg.getObject(2).toString(), GitHubRepository.class); } protected List<GitHubUser> toOrgList(JsArrayMixed arg) { List<GitHubUser> organizations = dtoFactory.createListDtoFromJson(arg.getObject(1).toString(), GitHubUser.class); organizations.add(dtoFactory.createDtoFromJson(arg.getObject(0).toString(), GitHubUser.class)); return organizations; } protected void onFailRequest(PromiseError arg) { showProcessing(false); if (arg.getCause() instanceof UnauthorizedException) { authorize(); } } /** * Authorizes on GitHub. */ private void authorize() { showProcessing(true); gitHubAuthenticator.authenticate( OAuth2AuthenticatorUrlProvider.get(baseUrl, "github", appContext.getCurrentUser().getProfile().getUserId(), Lists.asList("user", new String[]{"repo", "write:public_key"})), new AsyncCallback<OAuthStatus>() { @Override public void onFailure(Throwable caught) { showProcessing(false); } @Override public void onSuccess(OAuthStatus result) { showProcessing(false); getUserRepositoriesAndOrganizations(); } }); } @Override public void onRepositorySelected(@NotNull ProjectData repository) { dataObject.setName(repository.getName()); dataObject.setDescription(repository.getDescription()); dataObject.getSource().setLocation(repository.getRepositoryUrl()); view.setProjectName(repository.getName()); view.setProjectDescription(repository.getDescription()); view.setProjectUrl(repository.getRepositoryUrl()); updateDelegate.updateControls(); } @Override public void onAccountChanged() { refreshProjectList(); } /** * Perform actions when the list of repositories was loaded. * * @param gitHubRepositories * loaded list of repositories * @param gitHubOrganizations */ protected void onListLoaded(@NotNull List<GitHubUser> gitHubOrganizations, @NotNull List<GitHubRepository> gitHubRepositories) { this.repositories = new HashMap<>(); Map<String, String> login2OrgName = getLogin2OrgName(gitHubOrganizations); for (String orgName : login2OrgName.values()) { repositories.put(orgName, new ArrayList<GitHubRepository>()); } for (GitHubRepository gitHubRepository : gitHubRepositories) { String orgName = login2OrgName.get(gitHubRepository.getOwnerLogin()); if (orgName != null && repositories.containsKey(orgName)) { repositories.get(orgName).add(gitHubRepository); } } view.setAccountNames(repositories.keySet()); refreshProjectList(); view.showGithubPanel(); } private Map<String, String> getLogin2OrgName(List<GitHubUser> organizations) { Map<String, String> result = new HashMap<>(); for (GitHubUser gitHubUser : organizations) { String userName = gitHubUser.getName() != null ? gitHubUser.getName() : gitHubUser.getLogin(); result.put(gitHubUser.getLogin(), userName); } return result; } /** * Refresh project list on view. */ private void refreshProjectList() { List<ProjectData> projectsData = new ArrayList<>(); String accountName = view.getAccountName(); if (repositories.containsKey(accountName)) { List<GitHubRepository> repo = repositories.get(accountName); for (GitHubRepository repository : repo) { ProjectData projectData = new ProjectData(repository.getName(), repository.getDescription(), null, null, repository.getSshUrl(), repository.getGitUrl()); projectsData.add(projectData); } Collections.sort(projectsData, new Comparator<ProjectData>() { @Override public int compare(ProjectData o1, ProjectData o2) { return o1.getName().compareTo(o2.getName()); } }); view.setRepositories(projectsData); view.reset(); view.showGithubPanel(); } } /** * Shown the state that the request is processing. */ private void showProcessing(boolean inProgress) { view.setLoaderVisibility(inProgress); view.setInputsEnableState(!inProgress); } /** * Gets project name from uri. */ private String extractProjectNameFromUri(@NotNull String uri) { int indexFinishProjectName = uri.lastIndexOf("."); int indexStartProjectName = uri.lastIndexOf("/") != -1 ? uri.lastIndexOf("/") + 1 : (uri.lastIndexOf(":") + 1); if (indexStartProjectName != 0 && indexStartProjectName < indexFinishProjectName) { return uri.substring(indexStartProjectName, indexFinishProjectName); } if (indexStartProjectName != 0) { return uri.substring(indexStartProjectName); } return ""; } /** * Validate url * * @param url * url for validate * @return <code>true</code> if url is correct */ private boolean isGitUrlCorrect(@NotNull String url) { if (WHITE_SPACE.test(url)) { view.markURLInvalid(); view.setURLErrorMessage(locale.importProjectMessageStartWithWhiteSpace()); return false; } if (SCP_LIKE_SYNTAX.test(url)) { view.markURLValid(); view.setURLErrorMessage(null); return true; } if (!PROTOCOL.test(url)) { view.markURLInvalid(); view.setURLErrorMessage(locale.importProjectMessageProtocolIncorrect()); return false; } if (!(HOST1.test(url) || HOST2.test(url))) { view.markURLInvalid(); view.setURLErrorMessage(locale.importProjectMessageHostIncorrect()); return false; } if (!(REPO_NAME.test(url))) { view.markURLInvalid(); view.setURLErrorMessage(locale.importProjectMessageNameRepoIncorrect()); return false; } view.markURLValid(); view.setURLErrorMessage(null); return true; } }