PageRenderTime 71ms CodeModel.GetById 33ms RepoModel.GetById 1ms app.codeStats 0ms

/com/atlassian/jira/rpc/soap/service/IssueServiceImpl.java

https://bitbucket.org/atlassian_tutorial/jira-rpc-plugin
Java | 1467 lines | 1204 code | 162 blank | 101 comment | 162 complexity | 822232de2d038e1c68d6cab1f05ed9d6 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. package com.atlassian.jira.rpc.soap.service;
  2. import com.atlassian.core.util.map.EasyMap;
  3. import com.atlassian.crowd.embedded.api.User;
  4. import com.atlassian.jira.bc.JiraServiceContext;
  5. import com.atlassian.jira.bc.JiraServiceContextImpl;
  6. import com.atlassian.jira.bc.issue.attachment.AttachmentService;
  7. import com.atlassian.jira.bc.issue.comment.CommentService;
  8. import com.atlassian.jira.bc.issue.comment.DefaultCommentService;
  9. import com.atlassian.jira.bc.issue.worklog.*;
  10. import com.atlassian.jira.component.ComponentAccessor;
  11. import com.atlassian.jira.config.ConstantsManager;
  12. import com.atlassian.jira.config.LocaleManager;
  13. import com.atlassian.jira.config.SubTaskManager;
  14. import com.atlassian.jira.config.properties.ApplicationProperties;
  15. import com.atlassian.jira.datetime.DateTimeFormatter;
  16. import com.atlassian.jira.datetime.DateTimeFormatterFactory;
  17. import com.atlassian.jira.event.issue.IssueEventSource;
  18. import com.atlassian.jira.event.type.EventType;
  19. import com.atlassian.jira.exception.CreateException;
  20. import com.atlassian.jira.exception.DataAccessException;
  21. import com.atlassian.jira.exception.RemoveException;
  22. import com.atlassian.jira.issue.*;
  23. import com.atlassian.jira.issue.comments.Comment;
  24. import com.atlassian.jira.issue.comments.MutableComment;
  25. import com.atlassian.jira.issue.context.IssueContextImpl;
  26. import com.atlassian.jira.issue.context.ProjectContext;
  27. import com.atlassian.jira.issue.customfields.option.Option;
  28. import com.atlassian.jira.issue.customfields.option.OptionUtils;
  29. import com.atlassian.jira.issue.customfields.option.Options;
  30. import com.atlassian.jira.issue.fields.CustomField;
  31. import com.atlassian.jira.issue.fields.OrderableField;
  32. import com.atlassian.jira.issue.fields.config.FieldConfig;
  33. import com.atlassian.jira.issue.fields.layout.field.FieldLayout;
  34. import com.atlassian.jira.issue.fields.layout.field.FieldLayoutItem;
  35. import com.atlassian.jira.issue.fields.screen.FieldScreenRenderLayoutItem;
  36. import com.atlassian.jira.issue.fields.screen.FieldScreenRenderTab;
  37. import com.atlassian.jira.issue.fields.screen.FieldScreenRenderer;
  38. import com.atlassian.jira.issue.util.IssueUpdateBean;
  39. import com.atlassian.jira.issue.util.IssueUpdater;
  40. import com.atlassian.jira.issue.worklog.Worklog;
  41. import com.atlassian.jira.project.Project;
  42. import com.atlassian.jira.project.ProjectManager;
  43. import com.atlassian.jira.rpc.exception.RemoteException;
  44. import com.atlassian.jira.rpc.exception.RemotePermissionException;
  45. import com.atlassian.jira.rpc.exception.RemoteValidationException;
  46. import com.atlassian.jira.rpc.soap.beans.*;
  47. import com.atlassian.jira.rpc.soap.util.PluginSoapAttachmentHelper;
  48. import com.atlassian.jira.rpc.soap.util.SoapUtils;
  49. import com.atlassian.jira.rpc.soap.util.SoapUtilsBean;
  50. import com.atlassian.jira.security.JiraAuthenticationContext;
  51. import com.atlassian.jira.security.PermissionManager;
  52. import com.atlassian.jira.security.Permissions;
  53. import com.atlassian.jira.security.roles.ProjectRole;
  54. import com.atlassian.jira.user.preferences.JiraUserPreferences;
  55. import com.atlassian.jira.user.preferences.PreferenceKeys;
  56. import com.atlassian.jira.util.*;
  57. import com.atlassian.jira.web.action.issue.IssueCreationHelperBean;
  58. import com.atlassian.jira.web.util.AttachmentException;
  59. import com.atlassian.jira.workflow.IssueWorkflowManager;
  60. import com.atlassian.jira.workflow.WorkflowTransitionUtil;
  61. import com.atlassian.jira.workflow.WorkflowTransitionUtilImpl;
  62. import com.google.common.annotations.VisibleForTesting;
  63. import com.google.common.collect.Lists;
  64. import com.google.common.collect.Maps;
  65. import com.opensymphony.util.TextUtils;
  66. import com.opensymphony.workflow.loader.ActionDescriptor;
  67. import org.apache.commons.lang.StringUtils;
  68. import org.apache.log4j.Logger;
  69. import org.ofbiz.core.entity.GenericValue;
  70. import java.io.File;
  71. import java.util.*;
  72. import static com.atlassian.jira.datetime.DateTimeStyle.DATE_PICKER;
  73. public class IssueServiceImpl implements IssueService
  74. {
  75. private PermissionManager permissionManager;
  76. private ProjectManager projectManager;
  77. private IssueManager issueManager;
  78. private AttachmentManager attachmentManager;
  79. private ApplicationProperties applicationProperties;
  80. private final IssueFactory issueFactory;
  81. private final CommentService commentService;
  82. private final com.atlassian.jira.bc.projectroles.ProjectRoleService projectRoleService;
  83. private final IssueUpdater issueUpdater;
  84. private final AttachmentService attachmentService;
  85. private final com.atlassian.jira.bc.issue.IssueService issueService;
  86. private CustomFieldManager customFieldManager;
  87. private final LocaleManager localeManager;
  88. private PluginSoapAttachmentHelper attachmentHelper;
  89. private final IssueCreationHelperBean issueCreationHelperBean;
  90. private final DateTimeFormatterFactory dateTimeFormatterFactory;
  91. private final IssueWorkflowManager issueWorkflowManager;
  92. private final JiraAuthenticationContext authenticationContext;
  93. private final SoapUtilsBean soapUtilsBean;
  94. private final ConstantsManager constantsManager;
  95. private final WorklogService worklogService;
  96. private final SubTaskManager subTaskManager;
  97. private final IssueRetriever issueRetriever;
  98. private static final Logger log = Logger.getLogger(IssueServiceImpl.class);
  99. public static final String GENERIC_CONTENT_TYPE = "application/octet-stream";
  100. public IssueServiceImpl(PermissionManager permissionManager,
  101. ProjectManager projectManager,
  102. IssueManager issueManager,
  103. AttachmentManager attachmentManager,
  104. CustomFieldManager customFieldManager,
  105. IssueCreationHelperBean issueCreationHelperBean,
  106. DateTimeFormatterFactory dateTimeFormatterFactory,
  107. IssueWorkflowManager issueWorkflowManager,
  108. JiraAuthenticationContext authenticationContext,
  109. SoapUtilsBean soapUtilsBean,
  110. ConstantsManager constantsManager,
  111. LocaleManager localeManager,
  112. ApplicationProperties applicationProperties,
  113. IssueFactory issueFactory,
  114. CommentService commentService,
  115. com.atlassian.jira.bc.projectroles.ProjectRoleService projectRoleService,
  116. IssueUpdater issueUpdater,
  117. AttachmentService attachmentService,
  118. WorklogService worklogService,
  119. com.atlassian.jira.bc.issue.IssueService issueService,
  120. SubTaskManager subTaskManager,
  121. PluginSoapAttachmentHelper pluginSoapAttachmentHelper)
  122. {
  123. this.permissionManager = permissionManager;
  124. this.projectManager = projectManager;
  125. this.issueManager = issueManager;
  126. this.attachmentManager = attachmentManager;
  127. this.customFieldManager = customFieldManager;
  128. this.localeManager = localeManager;
  129. this.applicationProperties = applicationProperties;
  130. this.issueFactory = issueFactory;
  131. this.commentService = commentService;
  132. this.projectRoleService = projectRoleService;
  133. this.issueUpdater = issueUpdater;
  134. this.attachmentService = attachmentService;
  135. this.issueService = issueService;
  136. this.subTaskManager = subTaskManager;
  137. this.attachmentHelper = pluginSoapAttachmentHelper;
  138. this.issueCreationHelperBean = issueCreationHelperBean;
  139. this.dateTimeFormatterFactory = dateTimeFormatterFactory;
  140. this.issueWorkflowManager = issueWorkflowManager;
  141. this.authenticationContext = authenticationContext;
  142. this.soapUtilsBean = soapUtilsBean;
  143. this.constantsManager = constantsManager;
  144. this.worklogService = worklogService;
  145. this.issueRetriever = new IssueRetriever(issueManager, permissionManager);
  146. }
  147. public RemoteIssue getIssue(User user, String issueKey) throws RemoteException, RemotePermissionException
  148. {
  149. //JRA-13712: Need to set the user into the authenticationContext, as it may be used by certain fields during
  150. //issue constructions.
  151. final User oldUser = setRemoteUserInJira(user);
  152. try
  153. {
  154. final IssueRetriever.IssueInfo issueInfo = issueRetriever.retrieveIssue(issueKey, user);
  155. return new RemoteIssue(issueInfo.getIssue(), issueInfo.getParentIssue(), customFieldManager, attachmentManager, soapUtilsBean);
  156. }
  157. finally
  158. {
  159. setRemoteUserInJira(oldUser);
  160. }
  161. }
  162. public RemoteIssue createIssueWithSecurityLevel(final User user, final String parentIssueKey, final RemoteIssue rIssue, final Long securityLevelId)
  163. throws RemotePermissionException, RemoteValidationException, RemoteException
  164. {
  165. if (securityLevelId != null)
  166. {
  167. if (!permissionManager.hasPermission(Permissions.SET_ISSUE_SECURITY, projectManager.getProjectObjByKey(rIssue.getProject()), user))
  168. {
  169. throw new RemotePermissionException("This user does not have the 'set issue security' permission.");
  170. }
  171. }
  172. User oldUser = setRemoteUserInJira(user);
  173. try
  174. {
  175. final GenericValue project = validateRpcOnlyFields(rIssue, user);
  176. // sub task validation
  177. final Issue parentIssue;
  178. if (parentIssueKey != null)
  179. {
  180. parentIssue = validateSubTaskCreate(user, parentIssueKey, rIssue);
  181. }
  182. else
  183. {
  184. parentIssue = null;
  185. }
  186. // Go nuts with making a issue input params
  187. IssueInputParameters issueInputParameters = makeIssueInputParameters(project, rIssue, user, securityLevelId);
  188. // We want to indicate to the create validation which fields are provided and which need to be populated
  189. // with their default values
  190. // NOTE: this behaves slightly different from 4.0. Here we will not set the default value of a required field but we will
  191. // get it to run its validation. In 4.0 we would run its validation (which will always report an error if the value is not
  192. // provided and it is required) but we would then populate the field values holder with its default value.
  193. // The end result should be the same, an error exception reported to the user, it is just that the field
  194. // values holder will no longer contain the fields default value.
  195. final com.atlassian.jira.bc.issue.IssueService.CreateValidationResult createValidationResult;
  196. if (parentIssue != null)
  197. {
  198. createValidationResult = this.issueService.validateSubTaskCreate(user, parentIssue.getId(), issueInputParameters);
  199. }
  200. else
  201. {
  202. createValidationResult = this.issueService.validateCreate(user, issueInputParameters);
  203. }
  204. // Throw exception if there's a problem
  205. if (!createValidationResult.isValid())
  206. {
  207. final ErrorCollection errors = createValidationResult.getErrorCollection();
  208. throw new RemoteValidationException(errors.getErrors() + " : " + errors.getErrorMessages().toString());
  209. }
  210. final com.atlassian.jira.bc.issue.IssueService.IssueResult createIssueResult = issueService.create(user, createValidationResult);
  211. if (!createIssueResult.isValid())
  212. {
  213. final ErrorCollection errors = createIssueResult.getErrorCollection();
  214. throw new RemoteValidationException(errors.getErrors() + " : " + errors.getErrorMessages().toString());
  215. }
  216. final Issue createdIssue = createIssueResult.getIssue();
  217. if (parentIssue != null)
  218. {
  219. // I would argue this should be in the issue service but its not so
  220. // we do the same extra link step as the action does.
  221. try
  222. {
  223. subTaskManager.createSubTaskIssueLink(parentIssue, createdIssue, user);
  224. }
  225. catch (CreateException e)
  226. {
  227. throw new RemoteException(e);
  228. }
  229. }
  230. // Attach files if there are any
  231. if (rIssue.getAttachmentNames() != null)
  232. {
  233. addAttachmentToIssueFromMimeAttachments(user, rIssue.getAttachmentNames(), createdIssue);
  234. }
  235. final IssueRetriever.IssueInfo issueInfo = issueRetriever.retrieveIssue(createdIssue, user);
  236. return new RemoteIssue(issueInfo.getIssue(), issueInfo.getParentIssue(), customFieldManager, attachmentManager, soapUtilsBean);
  237. }
  238. catch (DataAccessException e)
  239. {
  240. throw new RemoteException("Error creating issue: " + e, e);
  241. }
  242. finally
  243. {
  244. setRemoteUserInJira(oldUser);
  245. }
  246. }
  247. private Issue validateSubTaskCreate(User user, String parentIssueKey, RemoteIssue rIssue) throws RemoteValidationException, RemotePermissionException
  248. {
  249. if (!subTaskManager.isSubTasksEnabled())
  250. {
  251. throw new RemoteValidationException("Subtasks are not enabled in this instance of JIRA");
  252. }
  253. final IssueRetriever.IssueInfo parentInfo = issueRetriever.retrieveIssue(parentIssueKey, user);
  254. final Issue parentIssue = parentInfo.getIssue();
  255. final String parentProjectKey = parentIssue.getProjectObject().getKey();
  256. // is it in the same project
  257. if (!parentProjectKey.equals(rIssue.getProject()))
  258. {
  259. throw new RemoteValidationException("The parent issue is not in the same project");
  260. }
  261. if (!parentIssue.isEditable())
  262. {
  263. throw new RemoteValidationException("The parent issue does not exist or is not editable");
  264. }
  265. // Subtasks of subtasks seems to work but is not supported yet. There are moves a foot to see this happen sooner rather than later
  266. // but for now we toe the line that sub tasks cant have subtasks.
  267. if (subTaskManager.isSubTask(parentIssue.getGenericValue()))
  268. {
  269. throw new RemoteValidationException("Parent is already a subtask : " + parentIssueKey);
  270. }
  271. return parentIssue;
  272. }
  273. private Collection<String> getProvidedFields(final User user, final GenericValue project, final IssueInputParameters issueInputParameters)
  274. {
  275. final Collection<String> providedFields = new ArrayList<String>();
  276. final MutableIssue contextIssue = issueFactory.getIssue();
  277. // Most calls using the issue object will fail unless the issue object has the project and issue type are set
  278. contextIssue.setProject(project);
  279. contextIssue.setIssueTypeId(issueInputParameters.getIssueTypeId());
  280. FieldScreenRenderer renderer = issueCreationHelperBean.createFieldScreenRenderer(user, contextIssue);
  281. FieldLayout fieldLayout = renderer.getFieldLayout();
  282. List visibleLayoutItems = fieldLayout.getVisibleLayoutItems(user, projectManager.getProjectObj(project.getLong("id")), EasyList.build(issueInputParameters.getIssueTypeId()));
  283. for (Iterator iterator = visibleLayoutItems.iterator(); iterator.hasNext();)
  284. {
  285. FieldLayoutItem fieldLayoutItem = (FieldLayoutItem) iterator.next();
  286. OrderableField orderableField = fieldLayoutItem.getOrderableField();
  287. final String fieldId = orderableField.getId();
  288. if (issueInputParameters.isFieldSet(fieldId) || fieldLayoutItem.isRequired())
  289. {
  290. providedFields.add(fieldId);
  291. }
  292. }
  293. return providedFields;
  294. }
  295. public RemoteIssue updateIssue(User user, String issueKey, Map actionParams) throws RemoteException
  296. {
  297. final Issue issueObject = issueRetriever.retrieveIssue(issueKey, user).getIssue();
  298. User oldUser = setRemoteUserInJira(user);
  299. try
  300. {
  301. final IssueInputParameters issueInputParameters = new IssueInputParametersImpl(actionParams);
  302. final Long projectId = issueObject.getProjectObject().getId();
  303. final List<CustomField> customFieldObjects = customFieldManager.getCustomFieldObjects(issueObject);
  304. for (final CustomField customField : customFieldObjects)
  305. {
  306. final Long customFieldId = customField.getIdAsLong();
  307. final String[] values = issueInputParameters.getCustomFieldValue(customFieldId);
  308. final FieldConfig fieldConfig = customField.getRelevantConfig(new IssueContextImpl(projectId,
  309. issueObject.getIssueTypeObject().getId()));
  310. final Options options = customField.getOptions(null, fieldConfig, new ProjectContext(projectId));
  311. if(values != null && options != null)
  312. {
  313. // If options are returned and they are in fact options then we should try and translate any strings to Option Ids
  314. // Versions and Projects (maybe more classes) erroneously implement Options but fill the list with random things and not instances of Option.
  315. if (options.isEmpty() || options.get(0) instanceof Option)
  316. {
  317. final String[] sanitisedValues = convertCustomFieldOptionValuesToOptionIds(options, values);
  318. issueInputParameters.addCustomFieldValue(customFieldId, sanitisedValues);
  319. }
  320. }
  321. }
  322. final com.atlassian.jira.bc.issue.IssueService.UpdateValidationResult validationResult = issueService.validateUpdate(user, issueObject.getId(), issueInputParameters);
  323. if (!validationResult.isValid())
  324. {
  325. throw new RemoteValidationException("Fields not valid for issue: \n" + validationResult.getErrorCollection());
  326. }
  327. final com.atlassian.jira.bc.issue.IssueService.IssueResult issueResult = issueService.update(user, validationResult);
  328. final IssueRetriever.IssueInfo issueInfo = issueRetriever.retrieveIssue(issueResult.getIssue(), user);
  329. return new RemoteIssue(issueInfo.getIssue(), issueInfo.getParentIssue(), customFieldManager, attachmentManager, soapUtilsBean);
  330. }
  331. finally
  332. {
  333. setRemoteUserInJira(oldUser);
  334. }
  335. }
  336. User setRemoteUserInJira(User user)
  337. {
  338. return soapUtilsBean.setRemoteUserInJira(user);
  339. }
  340. public RemoteIssue updateIssue(User user, String issueKey, RemoteFieldValue[] actionParams) throws RemoteException
  341. {
  342. return updateIssue(user, issueKey, soapUtilsBean.mapFieldValueToMap(actionParams));
  343. }
  344. public RemoteField[] getFieldsForCreate(User user, String projectKey, Long issueTypeId) throws RemoteException
  345. {
  346. //JRA-13703: Need to have the user available in the authenticationContext, such that fields
  347. // can carry out permission checks.
  348. final User oldUser = setRemoteUserInJira(user);
  349. try
  350. {
  351. if (issueTypeId == null)
  352. {
  353. throw new RemoteValidationException("The issue type must be specified");
  354. }
  355. Project project = projectManager.getProjectObjByKey(projectKey);
  356. if (project == null || !permissionManager.hasPermission(Permissions.CREATE_ISSUE, project, user))
  357. {
  358. throw new RemotePermissionException("You do not have create permission for this project.");
  359. }
  360. MutableIssue issue = issueFactory.getIssue();
  361. issue.setProjectId(project.getId());
  362. issue.setIssueTypeId(issueTypeId.toString());
  363. return soapUtilsBean.getFieldsForCreate(user, issue);
  364. }
  365. finally
  366. {
  367. setRemoteUserInJira(oldUser);
  368. }
  369. }
  370. public RemoteField[] getFieldsForEdit(User user, String issueKey) throws RemoteException
  371. {
  372. //JRA-13703: Need to have the user available in the authenticationContext, such that fields
  373. // can carry out permission checks.
  374. final User oldUser = setRemoteUserInJira(user);
  375. try
  376. {
  377. final Issue issueObject = issueRetriever.retrieveIssue(issueKey, user).getIssue();
  378. if (!permissionManager.hasPermission(Permissions.EDIT_ISSUE, issueObject, user))
  379. {
  380. throw new RemotePermissionException("You do not have edit permission for this issue.");
  381. }
  382. return soapUtilsBean.getFieldsForEdit(user, issueObject);
  383. }
  384. finally
  385. {
  386. setRemoteUserInJira(oldUser);
  387. }
  388. }
  389. private GenericValue validateRpcOnlyFields(RemoteIssue rIssue, User user)
  390. throws RemotePermissionException, RemoteValidationException
  391. {
  392. //if there is no project then the issue cannot be created
  393. final GenericValue project = projectManager.getProjectByKey(rIssue.getProject());
  394. if (project == null || !permissionManager.hasPermission(Permissions.CREATE_ISSUE, project, user))
  395. {
  396. throw new RemotePermissionException("The project specified does not exist or you don't have permission to create issues in it.");
  397. }
  398. if (StringUtils.isNotEmpty(rIssue.getId()))
  399. {
  400. throw new RemoteValidationException("You cannot specify an issue ID when creating an issue.");
  401. }
  402. if (StringUtils.isNotEmpty(rIssue.getKey()))
  403. {
  404. throw new RemoteValidationException("You cannot specify an issue key when creating an issue.");
  405. }
  406. if (StringUtils.isEmpty(rIssue.getType()))
  407. {
  408. throw new RemoteValidationException("No issue type specified.");
  409. }
  410. if (constantsManager.getIssueType(rIssue.getType()) == null)
  411. {
  412. throw new RemoteValidationException("Invalid issue type specified: " + rIssue.getType());
  413. }
  414. if (StringUtils.isEmpty(rIssue.getSummary()))
  415. {
  416. throw new RemoteValidationException("You must specify a summary when creating an issue.");
  417. }
  418. return project;
  419. }
  420. private IssueInputParameters makeIssueInputParameters(final GenericValue project, RemoteIssue rIssue, User user, Long securityLevelId)
  421. throws RemoteValidationException
  422. {
  423. IssueInputParameters issueInputParameters = new IssueInputParametersImpl();
  424. final Long projectId = project.getLong("id");
  425. issueInputParameters.setProjectId(projectId);
  426. issueInputParameters.setIssueTypeId(rIssue.getType());
  427. issueInputParameters.setSummary(rIssue.getSummary());
  428. if (StringUtils.isNotEmpty(rIssue.getReporter()))
  429. {
  430. issueInputParameters.setReporterId(rIssue.getReporter());
  431. }
  432. else
  433. {
  434. issueInputParameters.setReporterId(user.getName());
  435. }
  436. issueInputParameters.setAssigneeId(rIssue.getAssignee());
  437. issueInputParameters.setDescription(rIssue.getDescription());
  438. issueInputParameters.setEnvironment(rIssue.getEnvironment());
  439. issueInputParameters.setStatusId(rIssue.getStatus());
  440. issueInputParameters.setPriorityId(rIssue.getPriority());
  441. issueInputParameters.setResolutionId(rIssue.getResolution());
  442. issueInputParameters.setSecurityLevelId(securityLevelId);
  443. issueInputParameters.setFixVersionIds(SoapUtils.getRemoteEntityIdsAsLong(rIssue.getFixVersions()));
  444. issueInputParameters.setAffectedVersionIds(SoapUtils.getRemoteEntityIdsAsLong(rIssue.getAffectsVersions()));
  445. issueInputParameters.setComponentIds(SoapUtils.getRemoteEntityIdsAsLong(rIssue.getComponents()));
  446. // Setup the formatted due date
  447. if (dateTimeFormatterFactory != null)
  448. {
  449. JiraUserPreferences userPreferences = new JiraUserPreferences(user);
  450. String locale = userPreferences.getString(PreferenceKeys.USER_LOCALE);
  451. Locale userLocale;
  452. if (TextUtils.stringSet(locale))
  453. {
  454. userLocale = localeManager.getLocale(locale);
  455. }
  456. else
  457. {
  458. userLocale = applicationProperties.getDefaultLocale();
  459. }
  460. final DateTimeFormatter dateTimeFormatter = dateTimeFormatterFactory.formatter()
  461. .withLocale(userLocale)
  462. .withStyle(DATE_PICKER);
  463. if (rIssue.getDuedate() != null)
  464. {
  465. issueInputParameters.setDueDate(dateTimeFormatter.format(rIssue.getDuedate()));
  466. }
  467. }
  468. // Include all the custom fields
  469. final RemoteCustomFieldValue[] remoteCustomFieldValues = rIssue.getCustomFieldValues();
  470. if (remoteCustomFieldValues != null && remoteCustomFieldValues.length > 0)
  471. {
  472. for (RemoteCustomFieldValue remoteCustomFieldValue : remoteCustomFieldValues)
  473. {
  474. final String customfieldId = remoteCustomFieldValue.getCustomfieldId();
  475. final String key = remoteCustomFieldValue.getKey();
  476. CustomField customField = customFieldManager.getCustomFieldObject(customfieldId);
  477. if (customField != null)
  478. {
  479. String fullCfKey = customfieldId + (StringUtils.isEmpty(key) ? "" : ":" + remoteCustomFieldValue.getKey());
  480. final String[] values = remoteCustomFieldValue.getValues();
  481. final FieldConfig fieldConfig = customField.getRelevantConfig(new IssueContextImpl(projectId, rIssue.getType()));
  482. final Options options = customField.getOptions(null, fieldConfig, new ProjectContext(projectId));
  483. if(options != null && (options.isEmpty() || options.get(0) instanceof Option))
  484. {
  485. final String[] sanitisedValues = convertCustomFieldOptionValuesToOptionIds(options, values);
  486. issueInputParameters.addCustomFieldValue(fullCfKey, sanitisedValues);
  487. }
  488. else
  489. {
  490. issueInputParameters.addCustomFieldValue(fullCfKey, values);
  491. }
  492. }
  493. else
  494. {
  495. throw new RemoteValidationException("Custom field ID '" + remoteCustomFieldValue.getCustomfieldId() + "' is invalid.");
  496. }
  497. }
  498. }
  499. // Lets get the provided fields map and put it into the input parameters
  500. issueInputParameters.setProvidedFields(getProvidedFields(user, project, issueInputParameters));
  501. return issueInputParameters;
  502. }
  503. public void deleteIssue(User user, String issueKey) throws RemoteException, RemotePermissionException
  504. {
  505. final GenericValue project = projectManager.getProjectByKey(this.getIssue(user, issueKey).getProject());
  506. if (project == null || !permissionManager.hasPermission(Permissions.DELETE_ISSUE, project, user))
  507. {
  508. throw new RemotePermissionException("The project specified does not exist or you don't have permission to delete issues in it.");
  509. }
  510. try
  511. {
  512. final Issue issue = issueRetriever.retrieveIssue(issueKey, user).getIssue();
  513. com.atlassian.jira.bc.issue.IssueService.DeleteValidationResult validationResult = issueService.validateDelete(user, issue.getId());
  514. if (validationResult.isValid())
  515. {
  516. issueService.delete(user, validationResult);
  517. }
  518. else
  519. {
  520. throw new RemoveException("Delete action executed with errors");
  521. }
  522. }
  523. catch (Exception e)
  524. {
  525. throw new RemoteException("Unable to delete issue, cause: " + e.getMessage(), e);
  526. }
  527. }
  528. public RemoteComment[] getComments(User user, String issueKey) throws RemoteException, RemotePermissionException
  529. {
  530. ErrorCollection errorCollection = new SimpleErrorCollection();
  531. Issue issue = issueRetriever.retrieveIssue(issueKey, user).getIssue();
  532. RemoteComment[] comments = SoapUtils.getComments(commentService.getCommentsForUser(user, issue, errorCollection));
  533. checkAndThrowRemoteException(errorCollection);
  534. return comments;
  535. }
  536. public void addComment(User user, String issueKey, RemoteComment remoteComment)
  537. throws RemoteException, RemotePermissionException
  538. {
  539. ErrorCollection errorCollection = new SimpleErrorCollection();
  540. ProjectRole projectRole = null;
  541. final String roleLevel = remoteComment.getRoleLevel();
  542. if (StringUtils.isNotBlank(roleLevel))
  543. {
  544. projectRole = projectRoleService.getProjectRoleByName(user, roleLevel, errorCollection);
  545. if (projectRole == null)
  546. {
  547. throw new RemoteException("Project role: " + roleLevel + " does not exist");
  548. }
  549. }
  550. commentService.create(
  551. user,
  552. issueRetriever.retrieveIssue(issueKey, user).getIssue(),
  553. remoteComment.getBody(),
  554. remoteComment.getGroupLevel(),
  555. projectRole == null ? null : projectRole.getId(),
  556. remoteComment.getCreated(),
  557. true,
  558. errorCollection);
  559. checkAndThrowRemoteException(errorCollection);
  560. }
  561. public boolean hasPermissionToEditComment(User user, RemoteComment remoteComment) throws RemoteException
  562. {
  563. // Before we start using the comment we need to make sure it is somewhat valid
  564. if (remoteComment == null)
  565. {
  566. throw new RemoteException(getI18nHelper().getText(DefaultCommentService.ERROR_NULL_COMMENT));
  567. }
  568. if (remoteComment.getId() == null)
  569. {
  570. throw new RemoteException(getI18nHelper().getText(DefaultCommentService.ERROR_NULL_COMMENT_ID));
  571. }
  572. final ErrorCollection errorCollection = new SimpleErrorCollection();
  573. final Long commentId = (remoteComment.getId() == null) ? null : new Long(remoteComment.getId());
  574. final Comment comment = commentService.getMutableComment(user, commentId, errorCollection);
  575. checkAndThrowRemoteException(errorCollection);
  576. boolean hasPermissionToEdit = commentService.hasPermissionToEdit(user, comment, errorCollection);
  577. return !errorCollection.hasAnyErrors() && hasPermissionToEdit;
  578. }
  579. public RemoteComment editComment(User user, RemoteComment remoteComment) throws RemoteException
  580. {
  581. // Before we start using the comment we need to make sure it is somewhat valid
  582. if (remoteComment == null)
  583. {
  584. throw new RemoteException(getI18nHelper().getText(DefaultCommentService.ERROR_NULL_COMMENT));
  585. }
  586. if (remoteComment.getId() == null)
  587. {
  588. throw new RemoteException(getI18nHelper().getText(DefaultCommentService.ERROR_NULL_COMMENT_ID));
  589. }
  590. ErrorCollection errorCollection = new SimpleErrorCollection();
  591. ProjectRole projectRole = null;
  592. final String roleLevel = remoteComment.getRoleLevel();
  593. if (StringUtils.isNotBlank(roleLevel))
  594. {
  595. projectRole = projectRoleService.getProjectRoleByName(user, roleLevel, errorCollection);
  596. if (projectRole == null)
  597. {
  598. throw new RemoteException("Project role: " + roleLevel + " does not exist");
  599. }
  600. }
  601. final Long commentId = (remoteComment.getId() == null) ? null : new Long(remoteComment.getId());
  602. final Long projectRoleId = (projectRole == null) ? null : projectRole.getId();
  603. commentService.validateCommentUpdate(user, commentId, remoteComment.getBody(),
  604. remoteComment.getGroupLevel(), projectRoleId, errorCollection);
  605. // If validation produced any errors lets throw them out the user
  606. checkAndThrowRemoteException(errorCollection);
  607. final MutableComment mutableComment = commentService.getMutableComment(user, commentId, errorCollection);
  608. // If comment does not exist
  609. checkAndThrowRemoteException(errorCollection);
  610. Issue issue = mutableComment.getIssue();
  611. if (issue == null)
  612. {
  613. throw new RemoteException("No issue found for comment with id: " + commentId);
  614. }
  615. mutableComment.setBody(remoteComment.getBody());
  616. mutableComment.setGroupLevel(remoteComment.getGroupLevel());
  617. mutableComment.setRoleLevelId(projectRoleId);
  618. commentService.update(user, mutableComment, true, errorCollection);
  619. checkAndThrowRemoteException(errorCollection);
  620. return new RemoteComment(mutableComment);
  621. }
  622. public RemoteComment getComment(User user, Long commentId) throws RemoteException
  623. {
  624. ErrorCollection errorCollection = new SimpleErrorCollection();
  625. Comment comment = commentService.getCommentById(user, commentId, errorCollection);
  626. checkAndThrowRemoteException(errorCollection);
  627. return new RemoteComment(comment);
  628. }
  629. /**
  630. * Throws a RemoteValidation if errorCollection is non empty.
  631. *
  632. * @param errorCollection the ErrorCollection to check.
  633. * @throws RemoteValidationException if errorCollection is non empty.
  634. */
  635. private void checkAndThrowValidationException(ErrorCollection errorCollection) throws RemoteValidationException
  636. {
  637. if (errorCollection.hasAnyErrors())
  638. {
  639. throw new RemoteValidationException(errorCollection.toString());
  640. }
  641. }
  642. private void checkAndThrowRemoteException(ErrorCollection errorCollection) throws RemoteException
  643. {
  644. if (errorCollection.hasAnyErrors())
  645. {
  646. throw new RemoteException(errorCollection.toString());
  647. }
  648. }
  649. public RemoteNamedObject[] getAvailableActions(User user, String issueKey) throws RemoteException
  650. {
  651. User oldUser = setRemoteUserInJira(user);
  652. try
  653. {
  654. RemoteNamedObject[] actions = null;
  655. try
  656. {
  657. Collection<ActionDescriptor> descriptors = issueWorkflowManager.getAvailableActions(issueRetriever.retrieveIssue(issueKey, user).getIssue());
  658. if (descriptors != null && !descriptors.isEmpty())
  659. {
  660. actions = new RemoteNamedObject[descriptors.size()];
  661. int i = 0;
  662. for (final ActionDescriptor descriptor : descriptors)
  663. {
  664. int id = descriptor.getId();
  665. actions[i] = new RemoteNamedObject(String.valueOf(id), descriptor.getName());
  666. ++i;
  667. }
  668. }
  669. }
  670. catch (Exception e)
  671. {
  672. log.warn("Error loading available actions", e);
  673. }
  674. return actions;
  675. }
  676. finally
  677. {
  678. setRemoteUserInJira(oldUser);
  679. }
  680. }
  681. public RemoteField[] getFieldsForAction(User user, String issueKey, String actionIdString) throws RemoteException
  682. {
  683. User oldUser = setRemoteUserInJira(user);
  684. try
  685. {
  686. MutableIssue issue = (MutableIssue) issueRetriever.retrieveIssue(issueKey, user).getIssue();
  687. WorkflowTransitionUtil workflowTransitionUtil = getWorkflowTransitionUtil(issue, actionIdString);
  688. List fields = new ArrayList();
  689. if (workflowTransitionUtil.hasScreen())
  690. {
  691. for (Iterator iterator = workflowTransitionUtil.getFieldScreenRenderer().getFieldScreenRenderTabs().iterator(); iterator.hasNext();)
  692. {
  693. FieldScreenRenderTab fieldScreenRenderTab = (FieldScreenRenderTab) iterator.next();
  694. for (Iterator iterator1 = fieldScreenRenderTab.getFieldScreenRenderLayoutItemsForProcessing().iterator(); iterator1.hasNext();)
  695. {
  696. FieldScreenRenderLayoutItem fieldScreenRenderLayoutItem = (FieldScreenRenderLayoutItem) iterator1.next();
  697. if (fieldScreenRenderLayoutItem.isShow(issue))
  698. {
  699. OrderableField field = fieldScreenRenderLayoutItem.getOrderableField();
  700. fields.add(field);
  701. }
  702. }
  703. }
  704. }
  705. return soapUtilsBean.convertFieldsToRemoteFields(fields);
  706. }
  707. finally
  708. {
  709. setRemoteUserInJira(oldUser);
  710. }
  711. }
  712. public RemoteIssue progressWorkflowAction(User user, String issueKey, String actionIdString, RemoteFieldValue[] actionParams)
  713. throws RemoteException
  714. {
  715. return progressWorkflowAction(user, issueKey, actionIdString, soapUtilsBean.mapFieldValueToMap(actionParams));
  716. }
  717. public RemoteIssue getIssueById(User user, String issueId) throws RemoteException, RemotePermissionException
  718. {
  719. //JRA-13712: Need to set the user into the authenticationContext, as it may be used by certain fields during
  720. //issue constructions.
  721. final User oldUser = setRemoteUserInJira(user);
  722. try
  723. {
  724. // Cast the issue id to a Long and pass to the retrieve issue function
  725. IssueRetriever.IssueInfo issueInfo = issueRetriever.retrieveIssue(new Long(issueId), user);
  726. return new RemoteIssue(issueInfo.getIssue(), issueInfo.getParentIssue(), customFieldManager, attachmentManager, soapUtilsBean);
  727. }
  728. finally
  729. {
  730. setRemoteUserInJira(oldUser);
  731. }
  732. }
  733. private RemoteIssue progressWorkflowAction(User user, String issueKey, String actionIdString, Map actionParams)
  734. throws RemoteException
  735. {
  736. User oldUser = setRemoteUserInJira(user);
  737. try
  738. {
  739. MutableIssue issueObject = (MutableIssue) issueRetriever.retrieveIssue(issueKey, user).getIssue();
  740. WorkflowTransitionUtil workflowTransitionUtil = getWorkflowTransitionUtil(issueObject, actionIdString);
  741. // Validate params
  742. Map workflowTransitionParams = new HashMap();
  743. if (workflowTransitionUtil.hasScreen())
  744. {
  745. if (actionParams == null)
  746. {
  747. actionParams = Collections.EMPTY_MAP;
  748. }
  749. final IssueInputParameters issueInputParameters = new IssueInputParametersImpl(actionParams);
  750. final Long projectId = issueObject.getProjectObject().getId();
  751. final List<CustomField> customFieldObjects = customFieldManager.getCustomFieldObjects(issueObject);
  752. for (final CustomField customField : customFieldObjects)
  753. {
  754. final Long customFieldId = customField.getIdAsLong();
  755. final String[] values = issueInputParameters.getCustomFieldValue(customFieldId);
  756. final FieldConfig fieldConfig = customField.getRelevantConfig(new IssueContextImpl(projectId,
  757. issueObject.getIssueTypeObject().getId()));
  758. final Options options = customField.getOptions(null, fieldConfig, new ProjectContext(projectId));
  759. if(values != null && options != null)
  760. {
  761. // If options are returned and they are in fact options then we should try and translate any strings to Option Ids
  762. // Versions and Projects (maybe more classes) erroneously implement Options but fill the list with random things and not instances of Option.
  763. if (options.isEmpty() || options.get(0) instanceof Option)
  764. {
  765. final String[] sanitisedValues = convertCustomFieldOptionValuesToOptionIds(options, values);
  766. issueInputParameters.addCustomFieldValue(customFieldId, sanitisedValues);
  767. }
  768. }
  769. }
  770. for (Iterator iterator = workflowTransitionUtil.getFieldScreenRenderer().getFieldScreenRenderTabs().iterator(); iterator.hasNext();)
  771. {
  772. FieldScreenRenderTab fieldScreenRenderTab = (FieldScreenRenderTab) iterator.next();
  773. for (Iterator iterator1 = fieldScreenRenderTab.getFieldScreenRenderLayoutItemsForProcessing().iterator(); iterator1.hasNext();)
  774. {
  775. FieldScreenRenderLayoutItem fieldScreenRenderLayoutItem = (FieldScreenRenderLayoutItem) iterator1.next();
  776. if (fieldScreenRenderLayoutItem.isShow(issueObject))
  777. {
  778. OrderableField orderableField = fieldScreenRenderLayoutItem.getOrderableField();
  779. //JRA-16915: first popuplate the fieldvalues holder used for the transition from the value stored in the
  780. //issue. This is so that existing values don't get lost.
  781. orderableField.populateFromIssue(workflowTransitionParams, issueObject);
  782. //Only override the fields value if something was submitted in the SOAP request for this field.
  783. //Using the fieldId as the key into the actionParam map. This is safe, since the id is always
  784. //used to key the field in the parameter map (see implementations of
  785. // {@link com.atlassian.jira.issue.fields.AbstractOrderableField#getRelevantParams(Map params)}).
  786. if (issueInputParameters.isFieldPresent(orderableField.getId()))
  787. {
  788. // Then populate the field values holder with the action params submitted with the SOAP request
  789. // this should overwrite any values on the issue with what the user has submitted.
  790. orderableField.populateFromParams(workflowTransitionParams, issueInputParameters.getActionParameters());
  791. }
  792. }
  793. }
  794. }
  795. workflowTransitionUtil.setParams(workflowTransitionParams);
  796. ErrorCollection errors = workflowTransitionUtil.validate();
  797. if (errors.hasAnyErrors())
  798. {
  799. throw new RemoteValidationException("Fields not valid for workflow action " + workflowTransitionUtil.getActionDescriptor().getName() + ": \n" + errors);
  800. }
  801. }
  802. // Execute the workflow action
  803. ErrorCollection errors = workflowTransitionUtil.progress();
  804. if (errors.hasAnyErrors())
  805. {
  806. throw new RemoteException("Error occurred when running workflow action " + workflowTransitionUtil.getActionDescriptor().getName() + ": \n" + errors);
  807. }
  808. return getIssue(user, issueKey);
  809. }
  810. finally
  811. {
  812. setRemoteUserInJira(oldUser);
  813. }
  814. }
  815. public boolean addAttachmentsToIssue(User user, String issueKey, String[] fileNames, byte[][] attachments)
  816. throws RemoteException
  817. {
  818. Issue issue = issueRetriever.retrieveIssue(issueKey, user).getIssue();
  819. ErrorCollection errorCollection = new SimpleErrorCollection();
  820. JiraServiceContext serviceContext = new JiraServiceContextImpl(user, errorCollection);
  821. if (!attachmentService.canCreateAttachments(serviceContext, issue))
  822. {
  823. if (errorCollection.hasAnyErrors())
  824. {
  825. throw new RemoteValidationException("Can not create attachments.", errorCollection);
  826. }
  827. throw new RemotePermissionException("You do not have permission to create attachments, or attachments are not enabled in JIRA.");
  828. }
  829. if (attachments != null && fileNames != null)
  830. {
  831. if (attachments.length != fileNames.length)
  832. {
  833. throw new RemoteValidationException("Number of attachments (" + attachments.length + ") must match " +
  834. "the number of names (" + fileNames.length + ") passed.");
  835. }
  836. List changeItemBeans = new ArrayList();
  837. for (int i = 0; i < attachments.length; i++)
  838. {
  839. byte[] attachment = attachments[i];
  840. String fileName = fileNames[i];
  841. try
  842. {
  843. File tempFile = attachmentHelper.byteArrayToTempFile(attachment);
  844. changeItemBeans.add(attachmentManager.createAttachment(tempFile, fileName, GENERIC_CONTENT_TYPE, user, issue));
  845. }
  846. catch (AttachmentException e)
  847. {
  848. final String errorMsg = "Unable to attach file '" + fileName + "' through the SOAP interface";
  849. log.warn(errorMsg, e);
  850. throw new RemoteException(errorMsg, e);
  851. }
  852. }
  853. // if there are any change items, then update the issues change history
  854. if (!changeItemBeans.isEmpty())
  855. {
  856. IssueUpdateBean issueUpdateBean = new IssueUpdateBean(issue.getGenericValue(), issue.getGenericValue(), EventType.ISSUE_UPDATED_ID, user);
  857. issueUpdateBean.setChangeItems(changeItemBeans);
  858. issueUpdateBean.setDispatchEvent(true);
  859. issueUpdateBean.setParams(EasyMap.build("eventsource", IssueEventSource.ACTION));
  860. issueUpdater.doUpdate(issueUpdateBean, true);
  861. }
  862. }
  863. else
  864. {
  865. throw new RemoteException("Attachments / attachment names not specified");
  866. }
  867. return true;
  868. }
  869. public RemoteAttachment[] getAttachmentsFromIssue(User user, String issueKey) throws RemoteException
  870. {
  871. Issue issue = issueRetriever.retrieveIssue(issueKey, user).getIssue();
  872. return SoapUtils.getAttachments(attachmentManager.getAttachments(issue));
  873. }
  874. /**
  875. * Attaches files using the method outlined in Fear of attachments at http://www.iseran.com/Steve/papers/fear-of-attachments.pdf
  876. * and http://marc.theaimsgroup.com/?l=axis-user&m=109103923030924&w=2
  877. *
  878. * @return boolean
  879. */
  880. private boolean addAttachmentToIssueFromMimeAttachments(User user, String[] fileNames, Issue issueToAttach)
  881. throws RemoteException, RemotePermissionException
  882. {
  883. try
  884. {
  885. File[] files = attachmentHelper.saveFile(fileNames, issueToAttach);
  886. for (int i = 0; i < files.length; i++)
  887. {
  888. File file = files[i];
  889. attachmentManager.createAttachment(file,
  890. fileNames[i],
  891. GENERIC_CONTENT_TYPE,
  892. user,
  893. issueToAttach);
  894. }
  895. }
  896. catch (Exception e)
  897. {
  898. log.warn("Unable to attach files through the SOAP interface", e);
  899. throw new RemoteException("Unable to attach files.", e);
  900. }
  901. return true;
  902. }
  903. I18nHelper getI18nHelper()
  904. {
  905. return authenticationContext.getI18nHelper();
  906. }
  907. private WorkflowTransitionUtil getWorkflowTransitionUtil(MutableIssue issue, String actionIdString)
  908. throws RemoteException
  909. {
  910. try
  911. {
  912. int actionId = Integer.parseInt(actionIdString);
  913. WorkflowTransitionUtil workflowTransitionUtil = (WorkflowTransitionUtil) JiraUtils.loadComponent(WorkflowTransitionUtilImpl.class);
  914. workflowTransitionUtil.setIssue(issue);
  915. workflowTransitionUtil.setAction(actionId);
  916. workflowTransitionUtil.getActionDescriptor();
  917. return workflowTransitionUtil;
  918. }
  919. catch (NumberFormatException e)
  920. {
  921. throw new RemoteException(e);
  922. }
  923. catch (IllegalArgumentException e)
  924. {
  925. throw new RemoteException(e);
  926. }
  927. }
  928. /**
  929. * A wrapper to call through to the {@link com.atlassian.jira.bc.issue.worklog.WorklogService} to validate and
  930. * create a new worklog entry
  931. *
  932. * @param user the user in play
  933. * @param issueKey the key of the issue in play
  934. * @param remoteWorklog the remote worklog data
  935. * @param newRemainingEstimate a new remaning extimate
  936. * @return Created worklog with the id set or null if no worklog was created.
  937. * @throws RemoteException if anything goes wrong in general
  938. * @throws RemotePermissionException if the user does not have permission to adda worklog
  939. * @throws RemoteValidationException if the data does not pass validation
  940. */
  941. public RemoteWorklog addWorklogWithNewRemainingEstimate(final User user, final String issueKey, final RemoteWorklog remoteWorklog, final String newRemainingEstimate)
  942. throws RemoteException, RemotePermissionException, RemoteValidationException
  943. {
  944. JiraServiceContext serviceContext = new JiraServiceContextImpl(user, new SimpleErrorCollection());
  945. Issue issue = getIssueFromKey(issueKey);
  946. String timeSpent = remoteWorklog.getTimeSpent();
  947. Date startDate = remoteWorklog.getStartDate();
  948. String comment = remoteWorklog.getComment();
  949. String groupLevel = remoteWorklog.getGroupLevel();
  950. String roleLevelId = remoteWorklog.getR

Large files files are truncated, but you can click here to view the full file