PageRenderTime 43ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/Atlassian.Jira/Jira.cs

https://bitbucket.org/headspring/atlassian.net-sdk
C# | 502 lines | 321 code | 56 blank | 125 comment | 35 complexity | 19299d0887f92fa0d26f2b1d74c9de00 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using Atlassian.Jira.Remote;
  6. using Atlassian.Jira.Linq;
  7. using System.ServiceModel;
  8. using RestSharp;
  9. using Newtonsoft.Json.Linq;
  10. namespace Atlassian.Jira
  11. {
  12. /// <summary>
  13. /// Represents a JIRA server
  14. /// </summary>
  15. public class Jira
  16. {
  17. private const int DEFAULT_MAX_ISSUES_PER_REQUEST = 20;
  18. private const string ALL_PROJECTS_KEY = "[ALL_PROJECTS]";
  19. private const string REMOTE_AUTH_EXCEPTION_STRING = "com.atlassian.jira.rpc.exception.RemoteAuthenticationException";
  20. private readonly JiraQueryProvider _provider;
  21. private readonly IJiraRemoteService _jiraRemoteService;
  22. private readonly IFileSystem _fileSystem;
  23. private readonly string _username = null;
  24. private readonly string _password = null;
  25. private readonly bool _isAnonymous = false;
  26. private string _token = String.Empty;
  27. private Dictionary<string, IEnumerable<ProjectVersion>> _cachedVersions = new Dictionary<string,IEnumerable<ProjectVersion>>();
  28. private Dictionary<string, IEnumerable<ProjectComponent>> _cachedComponents = new Dictionary<string, IEnumerable<ProjectComponent>>();
  29. private Dictionary<string, IEnumerable<JiraNamedEntity>> _cachedFieldsForEdit = new Dictionary<string, IEnumerable<JiraNamedEntity>>();
  30. private Dictionary<string, IEnumerable<IssueType>> _cachedIssueTypes = new Dictionary<string, IEnumerable<IssueType>>();
  31. private IEnumerable<JiraNamedEntity> _cachedCustomFields = null;
  32. private IEnumerable<JiraNamedEntity> _cachedFilters = null;
  33. private IEnumerable<IssuePriority> _cachedPriorities = null;
  34. private IEnumerable<IssueStatus> _cachedStatuses = null;
  35. private IEnumerable<IssueResolution> _cachedResolutions = null;
  36. private IEnumerable<Project> _cachedProjects = null;
  37. /// <summary>
  38. /// Create a connection to a JIRA server with anonymous access
  39. /// </summary>
  40. /// <param name="url">Url to the JIRA server</param>
  41. public Jira(string url)
  42. : this(url, null, null)
  43. {
  44. }
  45. /// <summary>
  46. /// Create a connection to a JIRA server with provided credentials
  47. /// </summary>
  48. /// <param name="url">Url to the JIRA server</param>
  49. /// <param name="username">username to use to authenticate</param>
  50. /// <param name="password">passowrd to use to authenticate</param>
  51. public Jira(string url, string username, string password)
  52. :this(new JqlExpressionVisitor(),
  53. new JiraRemoteServiceWrapper(url, username, password),
  54. new FileSystem(),
  55. username,
  56. password)
  57. {
  58. }
  59. /// <summary>
  60. /// Create a connection to a JIRA server
  61. /// </summary>
  62. public Jira(IJqlExpressionVisitor translator,
  63. IJiraRemoteService jiraRemoteService,
  64. IFileSystem fileSystem,
  65. string username,
  66. string password)
  67. {
  68. _username = username;
  69. _password = password;
  70. _isAnonymous = String.IsNullOrEmpty(username) && String.IsNullOrEmpty(password);
  71. _jiraRemoteService = jiraRemoteService;
  72. _fileSystem = fileSystem;
  73. this.MaxIssuesPerRequest = DEFAULT_MAX_ISSUES_PER_REQUEST;
  74. this.Debug = false;
  75. this._provider = new JiraQueryProvider(translator, this);
  76. }
  77. internal IJiraRemoteService RemoteService
  78. {
  79. get
  80. {
  81. return _jiraRemoteService;
  82. }
  83. }
  84. /// <summary>
  85. /// Whether to print the translated JQL to console
  86. /// </summary>
  87. public bool Debug { get; set; }
  88. /// <summary>
  89. /// Whether to use the JIRA Rest API when querying for issues. Use only if targetting JIRA 5.0 or above.
  90. /// </summary>
  91. /// <remarks>
  92. /// Enables support for server side Count() and Skip() processing
  93. /// </remarks>
  94. public bool UseRestApi { get; set; }
  95. /// <summary>
  96. /// Maximum number of issues per request
  97. /// </summary>
  98. public int MaxIssuesPerRequest { get; set; }
  99. /// <summary>
  100. /// Url to the JIRA server
  101. /// </summary>
  102. public string Url
  103. {
  104. get { return _jiraRemoteService.Url; }
  105. }
  106. internal string UserName
  107. {
  108. get { return _username; }
  109. }
  110. internal string Password
  111. {
  112. get { return _password; }
  113. }
  114. internal IFileSystem FileSystem
  115. {
  116. get { return _fileSystem; }
  117. }
  118. /// <summary>
  119. /// Query the issues database
  120. /// </summary>
  121. /// <returns>IQueryable of Issue</returns>
  122. public JiraQueryable<Issue> Issues
  123. {
  124. get
  125. {
  126. return new JiraQueryable<Issue>(_provider);
  127. }
  128. }
  129. /// <summary>
  130. /// Gets an issue from the JIRA server
  131. /// </summary>
  132. /// <param name="key">The key of the issue</param>
  133. /// <returns></returns>
  134. public Issue GetIssue(string key)
  135. {
  136. return (from i in Issues
  137. where i.Key == key
  138. select i).First();
  139. }
  140. /// <summary>
  141. /// Returns issues that match the specified filter
  142. /// </summary>
  143. /// <param name="filterName">The name of the filter used for the search</param>
  144. /// <param name="start">The place in the result set to use as the first issue returned</param>
  145. /// <param name="maxResults">The maximum number of issues to return</param>
  146. public IEnumerable<Issue> GetIssuesFromFilter(string filterName, int start = 0, int? maxResults = null)
  147. {
  148. var filter = this.GetFilters().FirstOrDefault(f => f.Name.Equals(filterName, StringComparison.OrdinalIgnoreCase));
  149. if (filter == null)
  150. {
  151. throw new InvalidOperationException(String.Format("Filter with name '{0}' was not found", filterName));
  152. }
  153. return WithToken(token =>
  154. {
  155. return _jiraRemoteService.GetIssuesFromFilterWithLimit(token, filter.Id, start, maxResults ?? this.MaxIssuesPerRequest).Select(i => new Issue(this, i));
  156. });
  157. }
  158. /// <summary>
  159. /// Execute a specific JQL query and return the resulting issues
  160. /// </summary>
  161. /// <param name="jql">JQL search query</param>
  162. /// <returns>Collection of Issues that match the search query</returns>
  163. public IEnumerable<Issue> GetIssuesFromJql(string jql)
  164. {
  165. return GetIssuesFromJql(jql, 0, this.MaxIssuesPerRequest);
  166. }
  167. /// <summary>
  168. /// Execute a specific JQL query and return the resulting issues
  169. /// </summary>
  170. /// <param name="jql">JQL search query</param>
  171. /// <param name="maxResults">Maximum number of issues to return</param>
  172. /// <returns>Collection of Issues that match the search query</returns>
  173. public IEnumerable<Issue> GetIssuesFromJql(string jql, int startAt, int maxResults)
  174. {
  175. if (this.Debug)
  176. {
  177. Console.WriteLine("JQL: " + jql);
  178. }
  179. IList<Issue> issues = new List<Issue>();
  180. if (UseRestApi)
  181. {
  182. var json = JObject.Parse(_jiraRemoteService.GetJsonFromJqlSearch(jql, startAt, maxResults));
  183. foreach(var issue in (JArray)json["issues"])
  184. {
  185. issues.Add(Issue.FromJson(this, issue.ToString()));
  186. }
  187. }
  188. else if (startAt == 0)
  189. {
  190. WithToken(token =>
  191. {
  192. foreach (RemoteIssue remoteIssue in _jiraRemoteService.GetIssuesFromJqlSearch(token, jql, maxResults))
  193. {
  194. issues.Add(new Issue(this, remoteIssue));
  195. }
  196. });
  197. }
  198. else
  199. {
  200. throw new InvalidOperationException("'StartAt' cannot be used with SOAP API, set Jira.UseRestApi instead");
  201. }
  202. return issues;
  203. }
  204. /// <summary>
  205. /// Returns a new issue that when saved will be created on the remote JIRA server
  206. /// </summary>
  207. public Issue CreateIssue(string project, string parentIssueKey = null)
  208. {
  209. return new Issue(this, project, parentIssueKey);
  210. }
  211. /// <summary>
  212. /// Returns all the issue types within JIRA
  213. /// </summary>
  214. /// <param name="projectKey">If provided, returns issue types only for given project</param>
  215. /// <returns>Collection of JIRA issue types</returns>
  216. public IEnumerable<IssueType> GetIssueTypes(string projectKey = null)
  217. {
  218. string projectId = null;
  219. if (projectKey != null)
  220. {
  221. var project = this.GetProjects().FirstOrDefault(p => p.Key.Equals(projectKey, StringComparison.OrdinalIgnoreCase));
  222. if (project != null)
  223. {
  224. projectId = project.Id;
  225. }
  226. }
  227. projectKey = projectKey ?? ALL_PROJECTS_KEY;
  228. if (!_cachedIssueTypes.ContainsKey(projectKey))
  229. {
  230. WithToken(token =>
  231. {
  232. _cachedIssueTypes.Add(projectKey, _jiraRemoteService.GetIssueTypes(token, projectId).Select(t => new IssueType(t)));
  233. });
  234. }
  235. return _cachedIssueTypes[projectKey];
  236. }
  237. /// <summary>
  238. /// Returns all versions defined on a JIRA project
  239. /// </summary>
  240. /// <param name="projectKey">The project to retrieve the versions from</param>
  241. /// <returns>Collection of JIRA versions.</returns>
  242. public IEnumerable<ProjectVersion> GetProjectVersions(string projectKey)
  243. {
  244. if (!_cachedVersions.ContainsKey(projectKey))
  245. {
  246. WithToken(token =>
  247. {
  248. _cachedVersions.Add(projectKey, _jiraRemoteService.GetVersions(token, projectKey).Select(v => new ProjectVersion(v)));
  249. });
  250. }
  251. return _cachedVersions[projectKey];
  252. }
  253. /// <summary>
  254. /// Returns all components defined on a JIRA project
  255. /// </summary>
  256. /// <param name="projectKey">The project to retrieve the components from</param>
  257. /// <returns>Collection of JIRA components</returns>
  258. public IEnumerable<ProjectComponent> GetProjectComponents(string projectKey)
  259. {
  260. if (!_cachedComponents.ContainsKey(projectKey))
  261. {
  262. WithToken(token =>
  263. {
  264. _cachedComponents.Add(projectKey, _jiraRemoteService.GetComponents(token, projectKey).Select(c => new ProjectComponent(c)));
  265. });
  266. }
  267. return _cachedComponents[projectKey];
  268. }
  269. /// <summary>
  270. /// Returns all the issue priorities within JIRA
  271. /// </summary>
  272. /// <returns>Collection of JIRA issue priorities</returns>
  273. public IEnumerable<IssuePriority> GetIssuePriorities()
  274. {
  275. if (_cachedPriorities == null)
  276. {
  277. WithToken(token =>
  278. {
  279. _cachedPriorities = _jiraRemoteService.GetPriorities(token).Select(p => new IssuePriority(p));
  280. });
  281. }
  282. return _cachedPriorities;
  283. }
  284. /// <summary>
  285. /// Returns all the issue statuses within JIRA
  286. /// </summary>
  287. /// <returns>Collection of JIRA issue statuses</returns>
  288. public IEnumerable<IssueStatus> GetIssueStatuses()
  289. {
  290. if (_cachedStatuses == null)
  291. {
  292. WithToken(token =>
  293. {
  294. _cachedStatuses = _jiraRemoteService.GetStatuses(token).Select(s => new IssueStatus(s));
  295. });
  296. }
  297. return _cachedStatuses;
  298. }
  299. /// <summary>
  300. /// Returns all the issue resolutions within JIRA
  301. /// </summary>
  302. /// <returns>Collection of JIRA issue resolutions</returns>
  303. public IEnumerable<IssueResolution> GetIssueResolutions()
  304. {
  305. if (_cachedResolutions == null)
  306. {
  307. WithToken(token =>
  308. {
  309. _cachedResolutions = _jiraRemoteService.GetResolutions(token).Select(r => new IssueResolution(r));
  310. });
  311. }
  312. return _cachedResolutions;
  313. }
  314. /// <summary>
  315. /// Returns all custom fields within JIRA
  316. /// </summary>
  317. /// <returns>Collection of JIRA custom fields</returns>
  318. public IEnumerable<JiraNamedEntity> GetCustomFields()
  319. {
  320. if (_cachedCustomFields == null)
  321. {
  322. WithToken(token =>
  323. {
  324. _cachedCustomFields = _jiraRemoteService.GetCustomFields(token).Select(f => new JiraNamedEntity(f));
  325. });
  326. }
  327. return _cachedCustomFields;
  328. }
  329. /// <summary>
  330. /// Returns the favourite filters for the user
  331. /// </summary>
  332. public IEnumerable<JiraNamedEntity> GetFilters()
  333. {
  334. if (_cachedFilters == null)
  335. {
  336. WithToken(token =>
  337. {
  338. _cachedFilters = _jiraRemoteService.GetFavouriteFilters(token).Select(f => new JiraNamedEntity(f));
  339. });
  340. }
  341. return _cachedFilters;
  342. }
  343. /// <summary>
  344. /// Returns all projects defined in JIRA
  345. /// </summary>
  346. public IEnumerable<Project> GetProjects()
  347. {
  348. if (_cachedProjects == null)
  349. {
  350. WithToken(token =>
  351. {
  352. _cachedProjects = _jiraRemoteService.GetProjects(token).Select(p => new Project(p));
  353. });
  354. }
  355. return _cachedProjects;
  356. }
  357. /// <summary>
  358. /// Executes an action using the user's authentication token.
  359. /// </summary>
  360. /// <remarks>
  361. /// If action fails with 'com.atlassian.jira.rpc.exception.RemoteAuthenticationException'
  362. /// a new token will be requested from server and the action called again.
  363. /// </remarks>
  364. public void WithToken(Action<string> action)
  365. {
  366. WithToken<object>(t =>
  367. {
  368. action(t);
  369. return null;
  370. });
  371. }
  372. /// <summary>
  373. /// Executes an action using the user's authentication token and the jira soap client
  374. /// </summary>
  375. /// <remarks>
  376. /// If action fails with 'com.atlassian.jira.rpc.exception.RemoteAuthenticationException'
  377. /// a new token will be requested from server and the action called again.
  378. /// </remarks>
  379. public void WithToken(Action<string, IJiraRemoteService> action)
  380. {
  381. WithToken<object>((token, client) =>
  382. {
  383. action(token, client);
  384. return null;
  385. });
  386. }
  387. /// <summary>
  388. /// Executes a function using the user's authentication token.
  389. /// </summary>
  390. /// <remarks>
  391. /// If function fails with 'com.atlassian.jira.rpc.exception.RemoteAuthenticationException'
  392. /// a new token will be requested from server and the function called again.
  393. /// </remarks>
  394. public TResult WithToken<TResult>(Func<string, TResult> function)
  395. where TResult: class
  396. {
  397. return WithToken((token, client) => function(token));
  398. }
  399. /// <summary>
  400. /// Executes a function using the user's authentication token and the jira soap client
  401. /// </summary>
  402. /// <remarks>
  403. /// If function fails with 'com.atlassian.jira.rpc.exception.RemoteAuthenticationException'
  404. /// a new token will be requested from server and the function called again.
  405. /// </remarks>
  406. public TResult WithToken<TResult>(Func<string, IJiraRemoteService, TResult> function)
  407. {
  408. if (!_isAnonymous && String.IsNullOrEmpty(_token))
  409. {
  410. _token = _jiraRemoteService.Login(_username, _password);
  411. }
  412. try
  413. {
  414. return function(_token, this.RemoteService);
  415. }
  416. catch (FaultException fe)
  417. {
  418. if (_isAnonymous
  419. || fe.Message.IndexOf(REMOTE_AUTH_EXCEPTION_STRING, StringComparison.OrdinalIgnoreCase) < 0)
  420. {
  421. throw;
  422. }
  423. _token = _jiraRemoteService.Login(_username, _password);
  424. return function(_token, this.RemoteService);
  425. }
  426. }
  427. internal IEnumerable<JiraNamedEntity> GetFieldsForEdit(string projectKey)
  428. {
  429. if (!_cachedFieldsForEdit.ContainsKey(projectKey))
  430. {
  431. var tempIssue = this.GetIssuesFromJql(
  432. String.Format("project = \"{0}\"", projectKey),
  433. 0, 1).FirstOrDefault();
  434. if (tempIssue == null)
  435. {
  436. throw new InvalidOperationException("Project must contain at least one issue to be able to retrieve issue fields.");
  437. }
  438. WithToken(token =>
  439. {
  440. _cachedFieldsForEdit.Add(projectKey, _jiraRemoteService.GetFieldsForEdit(token, tempIssue.Key.Value).Select(f => new JiraNamedEntity(f)));
  441. });
  442. }
  443. return _cachedFieldsForEdit[projectKey];
  444. }
  445. }
  446. }