PageRenderTime 35ms CodeModel.GetById 0ms RepoModel.GetById 1ms app.codeStats 0ms

/src/org/bitbucket/connectors/jetbrains/BitbucketUtil.java

https://bitbucket.org/atlassian/jetbrains-bitbucket-connector/
Java | 426 lines | 366 code | 42 blank | 18 comment | 63 complexity | aa4bc54a01ad8929995c2f75f92fe5a7 MD5 | raw file
  1. package org.bitbucket.connectors.jetbrains;
  2. import com.intellij.openapi.fileChooser.FileChooser;
  3. import com.intellij.openapi.fileChooser.FileChooserDescriptor;
  4. import com.intellij.openapi.progress.ProgressIndicator;
  5. import com.intellij.openapi.progress.ProgressManager;
  6. import com.intellij.openapi.progress.Task;
  7. import com.intellij.openapi.project.Project;
  8. import com.intellij.openapi.ui.Messages;
  9. import com.intellij.openapi.util.Computable;
  10. import com.intellij.openapi.util.IconLoader;
  11. import com.intellij.openapi.util.Ref;
  12. import com.intellij.openapi.util.text.StringUtil;
  13. import com.intellij.openapi.vfs.VfsUtil;
  14. import com.intellij.openapi.vfs.VirtualFile;
  15. import com.intellij.openapi.vfs.VirtualFileManager;
  16. import com.intellij.util.SystemProperties;
  17. import com.intellij.util.net.HttpConfigurable;
  18. import org.apache.commons.httpclient.*;
  19. import org.apache.commons.httpclient.auth.AuthScope;
  20. import org.apache.commons.httpclient.methods.GetMethod;
  21. import org.apache.commons.httpclient.methods.PostMethod;
  22. import org.bitbucket.connectors.jetbrains.ui.BitbucketBundle;
  23. import org.bitbucket.connectors.jetbrains.ui.BitbucketLoginDialog;
  24. import org.bitbucket.connectors.jetbrains.ui.HtmlMessageDialog;
  25. import org.bitbucket.connectors.jetbrains.vcs.VcsHandler;
  26. import org.jdom.Element;
  27. import org.jdom.JDOMException;
  28. import org.jdom.input.SAXBuilder;
  29. import org.jetbrains.annotations.NotNull;
  30. import org.jetbrains.annotations.Nullable;
  31. import javax.swing.*;
  32. import java.awt.*;
  33. import java.io.IOException;
  34. import java.io.StringReader;
  35. import java.util.*;
  36. import java.util.List;
  37. public class BitbucketUtil {
  38. public static final String BITBUCKET = "Bitbucket";
  39. public static final String BITBUCKET_DN = "bitbucket.org";
  40. public static final String API_URL_BASE = "https://api." + BITBUCKET_DN + "/1.0";
  41. public static final Icon ICON = IconLoader.getIcon("res/bitbucket.png", BitbucketUtil.class);
  42. private static HttpClient getClient(String login, String password) {
  43. HttpClient client = new HttpClient();
  44. UsernamePasswordCredentials cred = new UsernamePasswordCredentials(login, password);
  45. client.getParams().setAuthenticationPreemptive(true);
  46. client.getState().setCredentials(AuthScope.ANY, cred);
  47. HttpConfigurable proxyConfig = HttpConfigurable.getInstance();
  48. if (proxyConfig.USE_HTTP_PROXY) {
  49. client.getHostConfiguration().setProxy(proxyConfig.PROXY_HOST, proxyConfig.PROXY_PORT);
  50. if (proxyConfig.PROXY_AUTHENTICATION) {
  51. UsernamePasswordCredentials proxyCred = new UsernamePasswordCredentials(proxyConfig.PROXY_LOGIN, proxyConfig.getPlainProxyPassword());
  52. client.getState().setProxyCredentials(AuthScope.ANY, proxyCred);
  53. }
  54. }
  55. return client;
  56. }
  57. public static Element request(String username, String password, String url, boolean post, @Nullable Map<String, String> params) throws IOException, JDOMException {
  58. return request(username, password, url, post, params, null);
  59. }
  60. public static Element request(String username, String password, String url, boolean post, @Nullable Map<String, String> params, @Nullable String query) throws IOException, JDOMException {
  61. url = "https://api." + BITBUCKET_DN + "/1.0" + url + "?format=xml";
  62. if (query != null)
  63. url = url + "&" + query;
  64. HttpClient client = getClient(username, password);
  65. HttpMethod res;
  66. if (post) {
  67. res = new PostMethod(url);
  68. if (params != null) {
  69. List<NameValuePair> pairs = new ArrayList<NameValuePair>();
  70. for (Map.Entry<String, String> entry: params.entrySet()) {
  71. pairs.add(new NameValuePair(entry.getKey(), entry.getValue()));
  72. }
  73. NameValuePair[] arr = new NameValuePair[pairs.size()];
  74. pairs.toArray(arr);
  75. ((PostMethod) res).setRequestBody(arr);
  76. }
  77. } else {
  78. res = new GetMethod(url);
  79. }
  80. client.executeMethod(res);
  81. String s = res.getResponseBodyAsString();
  82. return new SAXBuilder(false).build(new StringReader(s)).getRootElement();
  83. }
  84. /**
  85. * Returns list of repositories for logged user, or null if the login is cancelled.
  86. *
  87. * @param project project
  88. * @param ownOnly return own repositories only if true
  89. * @return list of repositories, or null on cancel
  90. */
  91. public static List<RepositoryInfo> getRepositories(Project project, final boolean ownOnly) {
  92. final BitbucketSettings settings = BitbucketSettings.getInstance();
  93. boolean logged;
  94. try {
  95. logged = executeWithProgressSynchronously(project, new Computable<Boolean>() {
  96. public Boolean compute() {
  97. ProgressManager.getInstance().getProgressIndicator().setText(BitbucketBundle.message("logging-bitbucket"));
  98. return testConnection(settings.getLogin(), settings.getPassword());
  99. }
  100. });
  101. } catch (CancelledException e) {
  102. return null;
  103. }
  104. if (!logged) {
  105. BitbucketLoginDialog dialog = new BitbucketLoginDialog(project);
  106. dialog.show();
  107. if (!dialog.isOK()) {
  108. return null;
  109. }
  110. }
  111. try {
  112. return executeWithProgressSynchronously(project, new Computable<List<RepositoryInfo>>() {
  113. public List<RepositoryInfo> compute() {
  114. ProgressManager.getInstance().getProgressIndicator().setText(BitbucketBundle.message("getting-repositories-list"));
  115. return getRepositories(settings.getLogin(), settings.getPassword(), ownOnly);
  116. }
  117. });
  118. } catch (CancelledException e) {
  119. return null;
  120. }
  121. }
  122. private static List<RepositoryInfo> getRepositories(String username, String password, boolean ownOnly) {
  123. List<RepositoryInfo> repositories = new ArrayList<RepositoryInfo>();
  124. try {
  125. Element element = request(username, password, "/user/repositories/", false, null);
  126. for (Element res : (List<Element>) element.getChildren("resource")) {
  127. RepositoryInfo info = new RepositoryInfo(res);
  128. if (!ownOnly || info.getOwner().equals(username)) {
  129. repositories.add(info);
  130. }
  131. }
  132. return repositories;
  133. } catch (Exception e) {
  134. // ignore
  135. }
  136. return repositories;
  137. }
  138. public static class CancelledException extends RuntimeException {
  139. }
  140. public static boolean checkCredentials(Project project, final String login, final String password) {
  141. if (login == null && password == null && areCredentialsEmpty()) {
  142. return false;
  143. }
  144. try {
  145. return executeWithProgressSynchronously(project, new Computable<Boolean>() {
  146. public Boolean compute() {
  147. ProgressManager.getInstance().getProgressIndicator().setText(BitbucketBundle.message("trying-login-bibucket"));
  148. if (login != null && password != null) {
  149. return testConnection(login, password);
  150. }
  151. BitbucketSettings settings = BitbucketSettings.getInstance();
  152. return testConnection(settings.getLogin(), settings.getPassword());
  153. }
  154. });
  155. }
  156. catch (CancelledException e) {
  157. return false;
  158. }
  159. }
  160. public static boolean areCredentialsEmpty() {
  161. BitbucketSettings settings = BitbucketSettings.getInstance();
  162. return StringUtil.isEmptyOrSpaces(settings.getLogin()) || StringUtil.isEmptyOrSpaces(settings.getPassword());
  163. }
  164. public static boolean testConnection(final String login, final String password) {
  165. try {
  166. Element element = request(login, password, "/emails/", false, null);
  167. List<Element> children = element.getChildren();
  168. return children.size() > 0 && children.get(0).getChildren().size() > 0;
  169. }
  170. catch (Exception e) {
  171. // Ignore
  172. }
  173. return false;
  174. }
  175. public static boolean sshEnabled(final Project project, final String login, final String password) {
  176. return executeWithProgressSynchronously(project, new Computable<Boolean>() {
  177. public Boolean compute() {
  178. return sshEnabled(login, password);
  179. }
  180. });
  181. }
  182. private static boolean sshEnabled(final String login, final String password) {
  183. try {
  184. Element element = request(login, password, "/ssh-keys/", false, null);
  185. List<Element> children = element.getChildren();
  186. return children.size() > 0 && children.get(0).getChild("key") != null;
  187. } catch (Exception e) {
  188. return false;
  189. }
  190. }
  191. /**
  192. * Shows SSH key selection dialog and uploads the selected file as Bitbucket key
  193. *
  194. * @param project
  195. * @param login
  196. * @param password
  197. *
  198. * @return true if uploaded successfully, false on upload error, null on cancel
  199. */
  200. public static Boolean addSshKey(final Project project, final Component parentComponent, final String login, final String password) {
  201. FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false) {
  202. @Override
  203. public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
  204. return file.isDirectory() || super.isFileVisible(file, showHiddenFiles);
  205. }
  206. };
  207. descriptor.setIsTreeRootVisible(true);
  208. String home = SystemProperties.getUserHome();
  209. VirtualFile root = null;
  210. if (home != null) {
  211. root = VirtualFileManager.getInstance().findFileByUrl("file://" + home);
  212. if (root != null) {
  213. VirtualFile ssh = root.findChild(".ssh"); // id_dsa.pub
  214. if (ssh == null) {
  215. ssh = root.findChild("Application Data/SSH/UserKeys");
  216. }
  217. if (ssh != null) {
  218. root = ssh;
  219. }
  220. }
  221. }
  222. descriptor.setTitle(BitbucketBundle.message("ssh-key-dialog-title"));
  223. descriptor.setDescription(BitbucketBundle.message("ssh-key-dialog-desc"));
  224. VirtualFile[] files = FileChooser.chooseFiles(descriptor, parentComponent, null, root);
  225. if (files.length == 0) {
  226. return null;
  227. }
  228. VirtualFile f = files[0];
  229. boolean result = false;
  230. try {
  231. final String key = VfsUtil.loadText(f);
  232. result = executeWithProgressSynchronously(project, new Computable<Boolean>() {
  233. public Boolean compute() {
  234. return addSshKey(login, password, key);
  235. }
  236. });
  237. } catch (IOException e1) {
  238. Messages.showErrorDialog("Can't read SSH key: " + f.getPath(), "SSH key");
  239. }
  240. return result;
  241. }
  242. private static boolean addSshKey(final String login, final String password, final String key) {
  243. try {
  244. Element element = request(login, password, "/ssh-keys/", true, Collections.singletonMap("key", key));
  245. return element.getChildren("key").size() > 0;
  246. } catch (Exception e) {
  247. return false;
  248. }
  249. }
  250. public static <T> T executeWithProgressSynchronously(final Project project, final Computable<T> computable) throws CancelledException {
  251. final Ref<T> result = new Ref<T>();
  252. ProgressManager.getInstance().run(new Task.Modal(project, BitbucketBundle.message("access-bitbucket"), true) {
  253. public void run(@NotNull ProgressIndicator indicator) {
  254. indicator.setIndeterminate(true);
  255. result.set(computable.compute());
  256. }
  257. @Override
  258. public void onCancel() {
  259. throw new CancelledException();
  260. }
  261. });
  262. return result.get();
  263. }
  264. public static void executeWithProgressSynchronously(final Project project, String title, final Runnable runnable) throws CancelledException {
  265. ProgressManager.getInstance().run(new Task.Modal(project, title, true) {
  266. public void run(@NotNull ProgressIndicator indicator) {
  267. indicator.setIndeterminate(true);
  268. runnable.run();
  269. }
  270. @Override
  271. public void onCancel() {
  272. throw new CancelledException();
  273. }
  274. });
  275. }
  276. public static void share(final Project project, final VirtualFile root, final String name, final String description, final boolean ssh, final boolean git, final VcsHandler vcsHandler) {
  277. final RepositoryInfo[] repo = new RepositoryInfo[1];
  278. final String title = BitbucketBundle.message("push-bitbucket", name);
  279. executeWithProgressSynchronously(project, title, new Runnable() {
  280. public void run() {
  281. BitbucketSettings settings = BitbucketSettings.getInstance();
  282. RepositoryInfo repository = createBitbucketRepository(settings.getLogin(), settings.getPassword(), name, description, true, git);
  283. if (repository != null && repository.isCreating()) {
  284. if (!waitRepositoryAvailable(settings, repository.getSlug())) {
  285. return;
  286. }
  287. }
  288. repo[0] = repository;
  289. }
  290. });
  291. final RepositoryInfo repository = repo[0];
  292. if (repository != null) {
  293. try {
  294. final String repositoryUrl = repository.getCheckoutUrl(ssh, true);
  295. vcsHandler.setRepositoryDefaultUrl(project, root, repository.getCheckoutUrl(ssh, false));
  296. new Task.Backgroundable(project, title, true) {
  297. public void run(@NotNull ProgressIndicator indicator) {
  298. if (!vcsHandler.push(project, root, repositoryUrl)) {
  299. return;
  300. }
  301. SwingUtilities.invokeLater(new Runnable() {
  302. public void run() {
  303. showPushedDialog(project, name, repository);
  304. }
  305. });
  306. }
  307. }.queue();
  308. } catch (Exception e) {
  309. throw new RuntimeException(e);
  310. }
  311. } else {
  312. Messages.showErrorDialog(project, BitbucketBundle.message("push-err"), BitbucketBundle.message("share-project-on-bitbucket"));
  313. }
  314. }
  315. private static void showPushedDialog(Project project, String name, RepositoryInfo repository) {
  316. try {
  317. HtmlMessageDialog dialog = new HtmlMessageDialog(project, BitbucketBundle.message("project-shared", name, repository.getCheckoutUrl()), BitbucketBundle.message("share-project-on-bitbucket"));
  318. dialog.show();
  319. } catch (URIException e) {
  320. Messages.showErrorDialog(project, e.getMessage(), BitbucketBundle.message("url-encode-err"));
  321. }
  322. }
  323. private static boolean waitRepositoryAvailable(BitbucketSettings settings, String name) {
  324. RepositoryInfo r;
  325. do {
  326. try {
  327. Thread.sleep(3000);
  328. r = getRepository(settings.getLogin(), settings.getPassword(), name);
  329. if (r == null) {
  330. return false;
  331. }
  332. } catch (InterruptedException e) {
  333. return false;
  334. }
  335. } while (r.isCreating());
  336. return true;
  337. }
  338. private static RepositoryInfo createBitbucketRepository(String login, String password, String name, String description, boolean isPrivate, boolean git) {
  339. Map<String, String> params = new HashMap<String, String>();
  340. params.put("name", name);
  341. params.put("description", description);
  342. if (isPrivate) {
  343. params.put("is_private", "True");
  344. }
  345. params.put("scm", git ? "git" : "hg");
  346. try {
  347. Element element = request(login, password, "/repositories/", true, params);
  348. return new RepositoryInfo(element);
  349. } catch (Exception e) {
  350. return null;
  351. }
  352. }
  353. public static RepositoryInfo getRepository(String login, String password, String name) {
  354. try {
  355. Element element = request(login, password, "/repositories/" + login + "/" + name + "/", false, null);
  356. return new RepositoryInfo(element);
  357. } catch (Exception e) {
  358. return null;
  359. }
  360. }
  361. public static boolean isSshUrl(String url) {
  362. return url != null && url.startsWith("ssh://");
  363. }
  364. public static boolean isHttpUrl(String url) {
  365. return url != null && (url.startsWith("http://") || url.startsWith("https://"));
  366. }
  367. public static boolean enableSsh(Project project, Component parent) {
  368. BitbucketSettings instance = BitbucketSettings.getInstance();
  369. if (!BitbucketUtil.sshEnabled(project, instance.getLogin(), instance.getPassword())) {
  370. if (Messages.showOkCancelDialog(project, BitbucketBundle.message("ssh-key-required"), BitbucketBundle.message("ssh-key-title"), null) == Messages.OK) {
  371. Boolean res = BitbucketUtil.addSshKey(project, parent, instance.getLogin(), instance.getPassword());
  372. if (Boolean.FALSE.equals(res)) {
  373. Messages.showErrorDialog(project, BitbucketBundle.message("ssh-key-invalid"), BitbucketBundle.message("ssh-key-title"));
  374. return false;
  375. } else if (res == null) {
  376. return false;
  377. }
  378. } else {
  379. return false;
  380. }
  381. }
  382. return true;
  383. }
  384. }