All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.taskadapter.redmineapi.bean.Issue Maven / Gradle / Ivy

Go to download

Free open-source Java API for Redmine and Chiliproject bug/task management systems. This project was originally a part of Task Adapter application (http://www.taskadapter.com) and then was open-sourced.

The newest version!
package com.taskadapter.redmineapi.bean;

import com.taskadapter.redmineapi.Include;
import com.taskadapter.redmineapi.NotFoundException;
import com.taskadapter.redmineapi.RedmineAuthenticationException;
import com.taskadapter.redmineapi.RedmineException;
import com.taskadapter.redmineapi.internal.RequestParam;
import com.taskadapter.redmineapi.internal.Transport;

import java.util.*;

/**
 * Redmine's Issue.
 * 

* Note that methods returning lists of elements (like getRelations(), getWatchers(), etc return * unmodifiable collections. * You need to use methods like addRelations() if you want to add elements, e.g.: *

 *     issue.addRelations(Collections.singletonList(relation));
 * 
* * @see http://www.redmine.org/projects/redmine/wiki/Rest_Issues */ public class Issue implements Identifiable, FluentStyle { private final PropertyStorage storage = new PropertyStorage(); public final static Property DATABASE_ID = new Property<>(Integer.class, "id"); public final static Property SUBJECT = new Property<>(String.class, "subject"); public final static Property START_DATE = new Property<>(Date.class, "startDate"); public final static Property DUE_DATE = new Property<>(Date.class, "dueDate"); public final static Property CREATED_ON = new Property<>(Date.class, "createdOn"); public final static Property UPDATED_ON = new Property<>(Date.class, "updatedOn"); public final static Property DONE_RATIO = new Property<>(Integer.class, "doneRatio"); public final static Property PARENT_ID = new Property<>(Integer.class, "parentId"); public final static Property PRIORITY_ID = new Property<>(Integer.class, "priorityId"); public final static Property ESTIMATED_HOURS = new Property<>(Float.class, "estimatedHours"); public final static Property SPENT_HOURS = new Property<>(Float.class, "spentHours"); public final static Property ASSIGNEE_ID = new Property<>(Integer.class, "assigneeId"); public final static Property ASSIGNEE_NAME = new Property<>(String.class, "assigneeName"); /** * Some comment describing an issue update. */ public final static Property NOTES = new Property(String.class, "notes"); public final static Property PRIVATE_NOTES = new Property<>(Boolean.class, "notes"); public final static Property PRIORITY_TEXT = new Property<>(String.class, "priorityText"); public final static Property PROJECT_ID = new Property<>(Integer.class, "projectId"); public final static Property PROJECT_NAME = new Property<>(String.class, "projectName"); public final static Property AUTHOR_ID = new Property<>(Integer.class, "authorId"); public final static Property AUTHOR_NAME = new Property<>(String.class, "authorName"); public final static Property TRACKER = new Property<>(Tracker.class, "tracker"); public final static Property DESCRIPTION = new Property<>(String.class, "description"); public final static Property CLOSED_ON = new Property<>(Date.class, "closedOn"); public final static Property STATUS_ID = new Property<>(Integer.class, "statusId"); public final static Property STATUS_NAME = new Property<>(String.class, "statusName"); public final static Property TARGET_VERSION = new Property<>(Version.class, "targetVersion"); public final static Property ISSUE_CATEGORY = new Property<>(IssueCategory.class, "issueCategory"); public final static Property PRIVATE_ISSUE = new Property<>(Boolean.class, "privateIssue"); /** * can't have two custom fields with the same ID in the collection, that's why it is declared * as a Set, not a List. */ public final static Property> CUSTOM_FIELDS = (Property>) new Property(Set.class, "customFields"); public final static Property> JOURNALS = (Property>) new Property(Set.class, "journals"); public final static Property> RELATIONS = (Property>) new Property(Set.class, "relations"); public final static Property> ATTACHMENTS = (Property>) new Property(Set.class, "attachments"); public final static Property> CHANGESETS = (Property>) new Property(Set.class, "changesets"); public final static Property> WATCHERS = (Property>) new Property(Set.class, "watchers"); public final static Property> CHILDREN = (Property>) new Property(Set.class, "children"); private Transport transport; public Issue() { initCollections(storage); } /** * Each Issue object must have project Id set in order for Redmine 3.x to accept it via REST API. */ public Issue(Transport transport, int projectId) { this(); this.transport = transport; setProjectId(projectId); } /** * @param projectId Each Issue object must have project Id set in order for Redmine 3.x to accept it via REST API. */ public Issue(Transport transport, int projectId, String subject) { this(); this.transport = transport; setSubject(subject); setProjectId(projectId); } private void initCollections(PropertyStorage storage) { storage.set(CUSTOM_FIELDS, new HashSet<>()); storage.set(CHILDREN, new HashSet<>()); storage.set(WATCHERS, new HashSet<>()); storage.set(CHANGESETS, new HashSet<>()); storage.set(ATTACHMENTS, new HashSet<>()); storage.set(RELATIONS, new HashSet<>()); storage.set(JOURNALS, new HashSet<>()); } public Integer getProjectId() { return storage.get(PROJECT_ID); } public Issue setProjectId(Integer projectId) { storage.set(PROJECT_ID, projectId); return this; } public String getProjectName() { return storage.get(PROJECT_NAME); } public Issue setProjectName(String name) { storage.set(PROJECT_NAME, name); return this; } /** * @param id database ID. */ public Issue setId(Integer id) { storage.set(DATABASE_ID, id); return this; } public Integer getDoneRatio() { return storage.get(DONE_RATIO); } public Issue setDoneRatio(Integer doneRatio) { storage.set(DONE_RATIO, doneRatio); return this; } public String getPriorityText() { return storage.get(PRIORITY_TEXT); } /** * @deprecated This method has no effect when creating issues on Redmine Server, so we might as well just delete it * in the future releases. */ public void setPriorityText(String priority) { storage.set(PRIORITY_TEXT, priority); } /** * Redmine can be configured to allow group assignments for issues: * Configuration option: Settings -> Issue Tracking -> Allow issue assignment to groups * *

An assignee can be a user or a group

*/ public Integer getAssigneeId() { return storage.get(ASSIGNEE_ID); } public Issue setAssigneeId(Integer assigneeId) { storage.set(ASSIGNEE_ID, assigneeId); return this; } public String getAssigneeName() { return storage.get(ASSIGNEE_NAME); } public Issue setAssigneeName(String assigneeName) { storage.set(ASSIGNEE_NAME, assigneeName); return this; } public Float getEstimatedHours() { return storage.get(ESTIMATED_HOURS); } public Issue setEstimatedHours(Float estimatedTime) { storage.set(ESTIMATED_HOURS, estimatedTime); return this; } public Float getSpentHours() { return storage.get(SPENT_HOURS); } public Issue setSpentHours(Float spentHours) { storage.set(SPENT_HOURS, spentHours); return this; } /** * Parent Issue ID, or NULL for issues without a parent. * * @return NULL, if there's no parent */ public Integer getParentId() { return storage.get(PARENT_ID); } public Issue setParentId(Integer parentId) { storage.set(PARENT_ID, parentId); return this; } /** * @return database id for this object. can be NULL for Issues not added to Redmine yet */ @Override public Integer getId() { return storage.get(DATABASE_ID); } public String getSubject() { return storage.get(SUBJECT); } public Issue setSubject(String subject) { storage.set(SUBJECT, subject); return this; } public Date getStartDate() { return storage.get(START_DATE); } public Issue setStartDate(Date startDate) { storage.set(START_DATE, startDate); return this; } public Date getDueDate() { return storage.get(DUE_DATE); } public Issue setDueDate(Date dueDate) { storage.set(DUE_DATE, dueDate); return this; } public Integer getAuthorId() { return storage.get(AUTHOR_ID); } /** * Marking as "deprecated": according to Redmine REST API docs * https://www.redmine.org/projects/redmine/wiki/Rest_Issues#Creating-an-issue , this parameter is not used * when creating issues (January 2020). */ @Deprecated public Issue setAuthorId(Integer id) { storage.set(AUTHOR_ID, id); return this; } public String getAuthorName() { return storage.get(AUTHOR_NAME); } /** * Marking as "deprecated": according to Redmine REST API docs * https://www.redmine.org/projects/redmine/wiki/Rest_Issues#Creating-an-issue , this parameter is not used * when creating issues (January 2020). */ @Deprecated public Issue setAuthorName(String name) { storage.set(AUTHOR_NAME, name); return this; } public Tracker getTracker() { return storage.get(TRACKER); } public Issue setTracker(Tracker tracker) { storage.set(TRACKER, tracker); return this; } public String getDescription() { return storage.get(DESCRIPTION); } public Issue setDescription(String description) { storage.set(DESCRIPTION, description); return this; } public Date getCreatedOn() { return storage.get(CREATED_ON); } public Issue setCreatedOn(Date createdOn) { storage.set(CREATED_ON, createdOn); return this; } public Date getUpdatedOn() { return storage.get(UPDATED_ON); } public Issue setUpdatedOn(Date updatedOn) { storage.set(UPDATED_ON, updatedOn); return this; } public Date getClosedOn() { return storage.get(CLOSED_ON); } public Issue setClosedOn(Date closedOn) { storage.set(CLOSED_ON, closedOn); return this; } public Integer getStatusId() { return storage.get(STATUS_ID); } public Issue setStatusId(Integer statusId) { storage.set(STATUS_ID, statusId); return this; } public String getStatusName() { return storage.get(STATUS_NAME); } public Issue setStatusName(String statusName) { storage.set(STATUS_NAME, statusName); return this; } /** * @return unmodifiable collection of Custom Field objects. the collection may be empty, but it is never NULL. */ public Collection getCustomFields() { return Collections.unmodifiableCollection(storage.get(CUSTOM_FIELDS)); } public Issue clearCustomFields() { storage.set(CUSTOM_FIELDS, new HashSet<>()); return this; } /** * NOTE: The custom field(s) must have correct database ID set to be saved to Redmine. * This is Redmine REST API's requirement. */ public Issue addCustomFields(Collection customFields) { storage.get(CUSTOM_FIELDS).addAll(customFields); return this; } /** * If there is a custom field with the same ID already present in the Issue, * the new field replaces the old one. * * @param customField the field to add to the issue. */ public Issue addCustomField(CustomField customField) { storage.get(CUSTOM_FIELDS).add(customField); return this; } @Deprecated /** * This method should not be used by clients. "notes" only makes sense when creating/updating an issue - that is the * string value added along with the update. *

* use {@link #getJournals()} if you want to access previously saved notes. feel free to submit an enhancement * request to Redmine developers if you think this "notes - journals" separation looks weird... */ public String getNotes() { return storage.get(NOTES); } /** * @param notes Some comment describing the issue update */ public Issue setNotes(String notes) { storage.set(NOTES, notes); return this; } public boolean isPrivateNotes() { return storage.get(PRIVATE_NOTES); } /** * @param privateNotes mark note as private */ public Issue setPrivateNotes(boolean privateNotes) { storage.set(PRIVATE_NOTES, privateNotes); return this; } /** * Don't forget to use Include.journals flag when loading issue from Redmine server: *

     *     Issue issue = issueManager.getIssueById(3205, Include.journals);
     * 
* @return unmodifiable collection of Journal entries or empty collection if no objects found. Never NULL. * @see com.taskadapter.redmineapi.Include#journals */ public Collection getJournals() { return Collections.unmodifiableCollection(storage.get(JOURNALS)); } /** * Issue journals are created automatically when you update existing issues. * journal entries are essentially log records for changes you make. * you cannot just add log records without making actual changes. * this API method is misleading and it should only be used internally by Redmine Json parser * when parsing response from server. we should hide it from public. * * TODO hide this method. https://github.com/taskadapter/redmine-java-api/issues/199 */ public void addJournals(Collection journals) { storage.get(JOURNALS).addAll(journals); } /** * Don't forget to use Include.changesets flag when loading issue from Redmine server: *
     *     Issue issue = issueManager.getIssueById(3205, Include.changesets);
     * 
* @return unmodifiable collection of entries or empty collection if no objects found. * @see com.taskadapter.redmineapi.Include#changesets */ public Collection getChangesets() { return Collections.unmodifiableCollection(storage.get(CHANGESETS)); } public Issue addChangesets(Collection changesets) { storage.get(CHANGESETS).addAll(changesets); return this; } /** * Don't forget to use Include.watchers flag when loading issue from Redmine server: *
     *     Issue issue = issueManager.getIssueById(3205, Include.watchers);
     * 
* @return unmodifiable collection of entries or empty collection if no objects found. * @see com.taskadapter.redmineapi.Include#watchers */ public Collection getWatchers() { return Collections.unmodifiableCollection(storage.get(WATCHERS)); } public Issue addWatchers(Collection watchers) { storage.get(WATCHERS).addAll(watchers); return this; } /** * Don't forget to use Include.children flag when loading issue from Redmine server: *
      *     Issue issue = issueManager.getIssueById(3205, Include.children);
      * 
* @return Collection of entries or empty collection if no objects found. * @see com.taskadapter.redmineapi.Include#children */ public Collection getChildren() { return Collections.unmodifiableCollection(storage.get(CHILDREN)); } public Issue addChildren(Collection children) { storage.get(CHILDREN).addAll(children); return this; } /** * Issues are considered equal if their IDs are equal. what about two issues with null ids? */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Issue issue = (Issue) o; if (getId() != null ? !getId().equals(issue.getId()) : issue.getId() != null) return false; return true; } @Override public int hashCode() { return getId() != null ? getId().hashCode() : 0; } /** * @return the custom field with given Id or NULL if the field is not found */ public CustomField getCustomFieldById(int customFieldId) { for (CustomField customField : storage.get(CUSTOM_FIELDS)) { if (customFieldId == customField.getId()) { return customField; } } return null; } /** * @return the custom field with given name or NULL if the field is not found */ public CustomField getCustomFieldByName(String customFieldName) { for (CustomField customField : storage.get(CUSTOM_FIELDS)) { if (customFieldName.equals(customField.getName())) { return customField; } } return null; } @Override public String toString() { return "Issue [id=" + getId() + ", subject=" + getSubject() + "]"; } /** * Relations are only loaded if you include Include.relations when loading the Issue. *
     *     Issue issue = issueManager.getIssueById(3205, Include.relations);
     * 
*

Since the returned collection is not modifiable, you need to use addRelations() method * if you want to add elements, e.g.: *

     *     issue.addRelations(Collections.singletonList(relation));
     * 
* @return unmodifiable collection of Relations or EMPTY collection if none found. Never returns NULL. * @see com.taskadapter.redmineapi.Include#relations */ public Collection getRelations() { return Collections.unmodifiableCollection(storage.get(RELATIONS)); } public Issue addRelations(Collection collection) { storage.get(RELATIONS).addAll(collection); return this; } public Integer getPriorityId() { return storage.get(PRIORITY_ID); } public Issue setPriorityId(Integer priorityId) { storage.set(PRIORITY_ID, priorityId); return this; } public Version getTargetVersion() { return storage.get(TARGET_VERSION); } /** * Don't forget to use Include.attachments flag when loading issue from Redmine server: *
     *     Issue issue = issueManager.getIssueById(3205, Include.attachments);
     * 
* @return unmodifiable collection of entries or empty collection if no objects found. * @see com.taskadapter.redmineapi.Include#attachments */ public Collection getAttachments() { return Collections.unmodifiableCollection(storage.get(ATTACHMENTS)); } public Issue addAttachments(Collection collection) { storage.get(ATTACHMENTS).addAll(collection); return this; } public Issue addAttachment(Attachment attachment) { storage.get(ATTACHMENTS).add(attachment); return this; } public Issue setTargetVersion(Version version) { storage.set(TARGET_VERSION, version); return this; } public IssueCategory getCategory() { return storage.get(ISSUE_CATEGORY); } public Issue setCategory(IssueCategory category) { storage.set(ISSUE_CATEGORY, category); return this; } /** * Default value is not determines. it's up to the server what it thinks the default value is if not set. */ public boolean isPrivateIssue() { return storage.get(PRIVATE_ISSUE); } public Issue setPrivateIssue(boolean privateIssue) { storage.set(PRIVATE_ISSUE, privateIssue); return this; } public PropertyStorage getStorage() { return storage; } /** * @return the newly created Issue. * * @throws RedmineAuthenticationException invalid or no API access key is used with the server, which * requires authorization. Check the constructor arguments. * @throws NotFoundException the required project is not found * @throws RedmineException */ public Issue create(RequestParam... params) throws RedmineException { RequestParam[] enrichParams = Arrays.copyOf(params, params.length + 1); enrichParams[params.length] = new RequestParam("include", Include.attachments.toString()); return transport.addObject(this, enrichParams); } public void update(RequestParam... params) throws RedmineException { transport.updateObject(this, params); } public void delete() throws RedmineException { transport.deleteObject(Issue.class, Integer.toString(this.getId())); } public void addWatcher(int watcherId) throws RedmineException { transport.addWatcherToIssue(watcherId, getId()); } public void deleteWatcher(int watcherId) throws RedmineException { transport.deleteChildId(Issue.class, Integer.toString(getId()), new Watcher(), watcherId); } @Override public void setTransport(Transport transport) { this.transport = transport; PropertyStorageUtil.updateCollections(storage, transport); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy