From f03a05e22ec3b9560b26baa3219862bce49cc6bb Mon Sep 17 00:00:00 2001 From: Daniel Stutz Date: Thu, 18 Jan 2018 16:03:04 +0100 Subject: [PATCH 1/5] Implement paging. --- .../jiraclient/agile/ResourceIterator.java | 214 ++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 src/main/java/net/rcarz/jiraclient/agile/ResourceIterator.java diff --git a/src/main/java/net/rcarz/jiraclient/agile/ResourceIterator.java b/src/main/java/net/rcarz/jiraclient/agile/ResourceIterator.java new file mode 100644 index 00000000..51ffcf5d --- /dev/null +++ b/src/main/java/net/rcarz/jiraclient/agile/ResourceIterator.java @@ -0,0 +1,214 @@ +package net.rcarz.jiraclient.agile; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import net.rcarz.jiraclient.Field; +import net.rcarz.jiraclient.JiraException; +import net.rcarz.jiraclient.RestClient; +import net.sf.json.JSON; +import net.sf.json.JSONObject; + +/** + * Iterates over all issues in the query by getting the next page of + * issues when the iterator reaches the last of the current page. + */ +public class ResourceIterator implements Iterator +{ + private Iterator currentPage; + private RestClient restclient; + private T nextIssue; + private Class type; + private String url; + private String listName; + private Integer maxResults; + private String jql; + private String includedFields; + private String expandFields; + private Integer startAt; + private List issues; + private boolean isLast; + private int total; + + public ResourceIterator(RestClient restclient, + Class type, + String url, + String listName, + String jql, + String includedFields, + String expandFields, + Integer maxResults, + Integer startAt) + throws JiraException + { + this.restclient = restclient; + this.type = type; + this.url = url; + this.listName = listName; + this.jql = jql; + this.includedFields = includedFields; + this.expandFields = expandFields; + this.maxResults = (maxResults == null ? 500 : maxResults); + this.startAt = startAt; + } + + @Override + public boolean hasNext() + { + if (nextIssue != null) + { + return true; + } + try + { + nextIssue = getNextIssue(); + } + catch (JiraException e) + { + throw new RuntimeException(e); + } + return nextIssue != null; + } + + @Override + public T next() + { + if (!hasNext()) + { + throw new NoSuchElementException(); + } + T result = nextIssue; + nextIssue = null; + return result; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException("Method remove() not support for class " + + this.getClass() + .getName()); + } + + /** + * Gets the next issue, returning null if none more available + * Will ask the next set of issues from the server if the end + * of the current list of issues is reached. + * + * @return the next issue, null if none more available + * @throws JiraException + */ + private T getNextIssue() + throws JiraException + { + // first call + if (currentPage == null) + { + currentPage = getNextIssues().iterator(); + if (currentPage == null || !currentPage.hasNext()) + { + return null; + } + else + { + return currentPage.next(); + } + } + + // check if we need to get the next set of issues + if (!currentPage.hasNext()) + { + currentPage = getNextIssues().iterator(); + } + + // return the next item if available + if (currentPage.hasNext()) + { + return currentPage.next(); + } + else + { + return null; + } + } + + /** + * Execute the query to get the next set of issues. + * Also sets the startAt, maxMresults, total and issues fields, + * so that the SearchResult can access them. + * + * @return the next set of issues. + * @throws JiraException + */ + private List getNextIssues() + throws JiraException + { + if (issues == null && startAt == null) + { + startAt = Integer.valueOf(0); + } + else if (issues != null) + { + startAt = startAt + issues.size(); + } + + if (isLast || (startAt != 0 && total != 0 && startAt >= total)) + return new ArrayList<>(); + + JSON result = null; + try + { + result = restclient.get(createURI()); + } + catch (Exception ex) + { + throw new JiraException("Failed to retrieve a list of " + type.getSimpleName() + " : " + url, ex); + } + + if (!(result instanceof JSONObject)) + { + throw new JiraException("JSON payload is malformed"); + } + + Map map = (Map) result; + this.isLast = Field.getBoolean(map.get("isLast")); + this.maxResults = Field.getInteger(map.get("maxResults")); + this.total = Field.getInteger(map.get("total")); + this.issues = AgileResource.getResourceArray(type, result, restclient, listName); + // System.out.println(startAt + " " + maxResults + " " + total + " " + isLast); + return issues; + } + + private URI createURI() + throws URISyntaxException + { + Map queryParams = new HashMap(); + queryParams.put("jql", jql); + if (maxResults != null) + { + queryParams.put("maxResults", String.valueOf(maxResults)); + } + if (includedFields != null) + { + queryParams.put("fields", includedFields); + } + if (expandFields != null) + { + queryParams.put("expand", expandFields); + } + if (startAt != null) + { + queryParams.put("startAt", String.valueOf(startAt)); + } + + URI uri = restclient.buildURI(url, queryParams); + // System.out.println(uri); + return uri; + } +} From 9c471f9798bfc459ef54438a01d314992ac0dad6 Mon Sep 17 00:00:00 2001 From: Daniel Stutz Date: Thu, 18 Jan 2018 16:07:58 +0100 Subject: [PATCH 2/5] Implement paging. --- .../rcarz/jiraclient/agile/AgileResource.java | 223 +++++++++++------- .../net/rcarz/jiraclient/agile/Board.java | 82 +++++-- .../java/net/rcarz/jiraclient/agile/Epic.java | 51 ++-- .../net/rcarz/jiraclient/agile/Issue.java | 98 +++++--- 4 files changed, 303 insertions(+), 151 deletions(-) diff --git a/src/main/java/net/rcarz/jiraclient/agile/AgileResource.java b/src/main/java/net/rcarz/jiraclient/agile/AgileResource.java index 8700618e..305d8d95 100644 --- a/src/main/java/net/rcarz/jiraclient/agile/AgileResource.java +++ b/src/main/java/net/rcarz/jiraclient/agile/AgileResource.java @@ -19,17 +19,18 @@ package net.rcarz.jiraclient.agile; +import java.lang.reflect.Constructor; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.math.NumberUtils; + import net.rcarz.jiraclient.Field; import net.rcarz.jiraclient.JiraException; import net.rcarz.jiraclient.RestClient; import net.sf.json.JSON; import net.sf.json.JSONArray; import net.sf.json.JSONObject; -import org.apache.commons.lang.math.NumberUtils; - -import java.lang.reflect.Constructor; -import java.util.ArrayList; -import java.util.List; /** * A base class for Agile resources. @@ -37,7 +38,8 @@ * @author pldupont * @see "https://docs.atlassian.com/jira-software/REST/cloud/" */ -public abstract class AgileResource { +public abstract class AgileResource +{ public static final String ATTR_ID = "id"; public static final String ATTR_NAME = "name"; @@ -55,12 +57,16 @@ public abstract class AgileResource { * Creates a new Agile resource. * * @param restclient REST client instance - * @param json JSON payload + * @param json JSON payload * @throws JiraException when the retrieval fails */ - public AgileResource(RestClient restclient, JSONObject json) throws JiraException { + public AgileResource(RestClient restclient, + JSONObject json) + throws JiraException + { this.restclient = restclient; - if (json != null) { + if (json != null) + { deserialize(json); } } @@ -68,26 +74,34 @@ public AgileResource(RestClient restclient, JSONObject json) throws JiraExceptio /** * Gets an Agile resource from the given object. * - * @param type Resource data type - * @param r a JSONObject instance + * @param type Resource data type + * @param r a JSONObject instance * @param restclient REST client instance * @return a Resource instance or null if r isn't a JSONObject instance * @throws JiraException when the retrieval fails */ - protected static T getResource( - Class type, Object r, RestClient restclient) throws JiraException { - - if (!(r instanceof JSONObject)) { + protected static T getResource(Class type, + Object r, + RestClient restclient) + throws JiraException + { + + if (!(r instanceof JSONObject)) + { throw new JiraException("JSON payload is malformed"); } T result = null; - if (!((JSONObject) r).isNullObject()) { - try { + if (!((JSONObject) r).isNullObject()) + { + try + { Constructor constructor = type.getDeclaredConstructor(RestClient.class, JSONObject.class); result = constructor.newInstance(restclient, r); - } catch (Exception e) { + } + catch (Exception e) + { throw new JiraException("Failed to deserialize object.", e); } } @@ -98,30 +112,38 @@ protected static T getResource( /** * Gets a list of GreenHopper resources from the given object. * - * @param type Resource data type - * @param ra a JSONArray instance + * @param type Resource data type + * @param ra a JSONArray instance * @param restclient REST client instance - * @param listName The name of the list of items from the JSON result. + * @param listName The name of the list of items from the JSON result. * @return a list of Resources found in ra * @throws JiraException when the retrieval fails */ - protected static List getResourceArray( - Class type, Object ra, RestClient restclient, String listName) throws JiraException { - if (!(ra instanceof JSONObject)) { + protected static List getResourceArray(Class type, + Object ra, + RestClient restclient, + String listName) + throws JiraException + { + if (!(ra instanceof JSONObject)) + { throw new JiraException("JSON payload is malformed"); } JSONObject jo = (JSONObject) ra; - if (!jo.containsKey(listName) || !(jo.get(listName) instanceof JSONArray)) { + if (!jo.containsKey(listName) || !(jo.get(listName) instanceof JSONArray)) + { throw new JiraException("No array found for name '" + listName + "'"); } List results = new ArrayList(); - for (Object v : (JSONArray) jo.get(listName)) { + for (Object v : (JSONArray) jo.get(listName)) + { T item = getResource(type, v, restclient); - if (item != null) { + if (item != null) + { results.add(item); } } @@ -133,42 +155,47 @@ protected static List getResourceArray( * Retrieves all boards visible to the session user. * * @param restclient REST client instance - * @param type The type of the object to deserialize. - * @param url The URL to call. + * @param type The type of the object to deserialize. + * @param url The URL to call. * @return a list of boards * @throws JiraException when the retrieval fails */ - static List list( - RestClient restclient, Class type, String url) throws JiraException { - return list(restclient, type, url, "values"); + static List list(RestClient restclient, + Class type, + String url, + String... jql) + throws JiraException + { + return list(restclient, type, url, "values", jql.length > 0 ? jql[0] : ""); } /** * Retrieves all boards visible to the session user. * * @param restclient REST client instance - * @param type The type of the object to deserialize. - * @param url The URL to call. - * @param listName The name of the list of items in the JSON response. + * @param type The type of the object to deserialize. + * @param url The URL to call. + * @param listName The name of the list of items in the JSON response. * @return a list of boards * @throws JiraException when the retrieval fails */ - static List list( - RestClient restclient, Class type, String url, String listName) throws JiraException { - - JSON result; - try { - result = restclient.get(url); - } catch (Exception ex) { - throw new JiraException("Failed to retrieve a list of " + type.getSimpleName() + " : " + url, ex); + static List list(RestClient restclient, + Class type, + String url, + String listName, + String jql) + throws JiraException + { + + final ResourceIterator iter = + new ResourceIterator(restclient, type, url, listName, jql, null, null, null, null); + + List list = new ArrayList<>(); + while (iter.hasNext()) + { + list.add((T) iter.next()); } - - return getResourceArray( - type, - result, - restclient, - listName - ); + return list; } /** @@ -178,36 +205,43 @@ static List list( * @return a list of boards * @throws JiraException when the retrieval fails */ - static T get(RestClient restclient, Class type, String url) throws JiraException { + static T get(RestClient restclient, + Class type, + String url) + throws JiraException + { JSON result; - try { + try + { result = restclient.get(url); - } catch (Exception ex) { + } + catch (Exception ex) + { throw new JiraException("Failed to retrieve " + type.getSimpleName() + " : " + url, ex); } - return getResource( - type, - result, - restclient - ); + return getResource(type, result, restclient); } /** * Extract from a sub list the Resource array, if present. * - * @param type Resource data type - * @param subJson a JSONObject instance + * @param type Resource data type + * @param subJson a JSONObject instance * @param resourceName The name of the list of items from the JSON result. - * @param The type of Agile resource to return. + * @param The type of Agile resource to return. * @return The list of resources if present. * @throws JiraException when the retrieval fails */ - List getSubResourceArray( - Class type, JSONObject subJson, String resourceName) throws JiraException { + List getSubResourceArray(Class type, + JSONObject subJson, + String resourceName) + throws JiraException + { List result = null; - if (subJson.containsKey(resourceName)) { + if (subJson.containsKey(resourceName)) + { result = getResourceArray(type, subJson.get(resourceName), getRestclient(), resourceName + "s"); } return result; @@ -216,17 +250,23 @@ List getSubResourceArray( /** * Extract from a sub list the Resource, if present. * - * @param type Resource data type - * @param subJson a JSONObject instance + * @param type Resource data type + * @param subJson a JSONObject instance * @param resourceName The name of the item from the JSON result. - * @param The type of Agile resource to return. + * @param The type of Agile resource to return. * @return The resource if present. * @throws JiraException when the retrieval fails */ - T getSubResource( - Class type, JSONObject subJson, String resourceName) throws JiraException { + T getSubResource(Class type, + JSONObject subJson, + String resourceName) + throws JiraException + { T result = null; - if (subJson.containsKey(resourceName) && !subJson.get(resourceName).equals("null")) { + if (subJson.containsKey(resourceName) + && !subJson.get(resourceName) + .equals("null")) + { result = getResource(type, subJson.get(resourceName), getRestclient()); } return result; @@ -235,35 +275,40 @@ T getSubResource( /** * @return Internal JIRA ID. */ - public long getId() { + public long getId() + { return id; } /** * @return The resource name. */ - public String getName() { + public String getName() + { return name; } /** * @param name Setter for the resource name. In some case, the name is called something else. */ - void setName(String name) { + void setName(String name) + { this.name = name; } /** * @return The resource URL. */ - public String getSelfURL() { + public String getSelfURL() + { return self; } /** * @return The REST client used to access the current resource. */ - protected RestClient getRestclient() { + protected RestClient getRestclient() + { return restclient; } @@ -273,7 +318,8 @@ protected RestClient getRestclient() { * @param name The name of the attribute to retrieve. * @return The value of the attribute. */ - public Object getAttribute(String name) { + public Object getAttribute(String name) + { return attributes.get(name); } @@ -283,7 +329,9 @@ public Object getAttribute(String name) { * * @param json The JSON object to read. */ - void deserialize(JSONObject json) throws JiraException { + void deserialize(JSONObject json) + throws JiraException + { id = getLong(json.get("id")); name = Field.getString(json.get("name")); @@ -296,23 +344,30 @@ void deserialize(JSONObject json) throws JiraException { * * @param json The json object to extract attributes from. */ - void addAttributes(JSONObject json) { + void addAttributes(JSONObject json) + { attributes.putAll(json); } - long getLong(Object o) { - if (o instanceof Integer || o instanceof Long) { + long getLong(Object o) + { + if (o instanceof Integer || o instanceof Long) + { return Field.getLong(o); - } else if (o instanceof String && NumberUtils.isDigits((String) o)) { + } + else if (o instanceof String && NumberUtils.isDigits((String) o)) + { return NumberUtils.toLong((String) o, 0L); - } else { + } + else + { return 0L; } } @Override - public String toString() { + public String toString() + { return String.format("%s{id=%s, name='%s'}", getClass().getSimpleName(), id, name); } } - diff --git a/src/main/java/net/rcarz/jiraclient/agile/Board.java b/src/main/java/net/rcarz/jiraclient/agile/Board.java index 4db6aeca..301f354a 100644 --- a/src/main/java/net/rcarz/jiraclient/agile/Board.java +++ b/src/main/java/net/rcarz/jiraclient/agile/Board.java @@ -19,19 +19,20 @@ package net.rcarz.jiraclient.agile; +import java.util.List; + import net.rcarz.jiraclient.Field; import net.rcarz.jiraclient.JiraException; import net.rcarz.jiraclient.RestClient; import net.sf.json.JSONObject; -import java.util.List; - /** * Represents an Agile Board. * * @author pldupont */ -public class Board extends AgileResource { +public class Board extends AgileResource +{ private String type; @@ -39,9 +40,12 @@ public class Board extends AgileResource { * Creates a Board from a JSON payload. * * @param restclient REST client instance - * @param json JSON payload + * @param json JSON payload */ - protected Board(RestClient restclient, JSONObject json) throws JiraException { + protected Board(RestClient restclient, + JSONObject json) + throws JiraException + { super(restclient, json); } @@ -49,11 +53,14 @@ protected Board(RestClient restclient, JSONObject json) throws JiraException { * Retrieves the given rapid view. * * @param restclient REST client instance - * @param id Internal JIRA ID of the rapid view + * @param id Internal JIRA ID of the rapid view * @return a rapid view instance * @throws JiraException when the retrieval fails */ - public static Board get(RestClient restclient, long id) throws JiraException { + public static Board get(RestClient restclient, + long id) + throws JiraException + { return AgileResource.get(restclient, Board.class, RESOURCE_URI + "board/" + id); } @@ -64,12 +71,17 @@ public static Board get(RestClient restclient, long id) throws JiraException { * @return a list of boards * @throws JiraException when the retrieval fails */ - public static List getAll(RestClient restclient) throws JiraException { - return AgileResource.list(restclient, Board.class, RESOURCE_URI + "board"); + public static List getAll(RestClient restclient, + String... jql) + throws JiraException + { + return AgileResource.list(restclient, Board.class, RESOURCE_URI + "board", jql.length > 0 ? jql[0] : ""); } @Override - protected void deserialize(JSONObject json) throws JiraException { + protected void deserialize(JSONObject json) + throws JiraException + { super.deserialize(json); type = Field.getString(json.get("type")); } @@ -77,7 +89,8 @@ protected void deserialize(JSONObject json) throws JiraException { /** * @return The board type. */ - public String getType() { + public String getType() + { return this.type; } @@ -85,7 +98,9 @@ public String getType() { * @return All sprints related to the current board. * @throws JiraException when the retrieval fails */ - public List getSprints() throws JiraException { + public List getSprints() + throws JiraException + { return Sprint.getAll(getRestclient(), getId()); } @@ -93,24 +108,53 @@ public List getSprints() throws JiraException { * @return All issues in the Board backlog. * @throws JiraException when the retrieval fails */ - public List getBacklog() throws JiraException { - return AgileResource.list(getRestclient(), Issue.class, RESOURCE_URI + "board/" + getId() + "/backlog", "issues"); + public List getBacklog() + throws JiraException + { + return AgileResource.list(getRestclient(), + Issue.class, + RESOURCE_URI + "board/" + getId() + "/backlog", + "issues"); } /** * @return All issues without epic in the Board . * @throws JiraException when the retrieval fails */ - public List getIssuesWithoutEpic() throws JiraException { - return AgileResource.list(getRestclient(), Issue.class, RESOURCE_URI + "board/" + getId() + "/epic/none/issue", "issues"); + public List getIssuesWithoutEpic(String... jql) + throws JiraException + { + return AgileResource.list(getRestclient(), + Issue.class, + RESOURCE_URI + "board/" + getId() + "/epic/none/issue", + "issues", + jql.length > 0 ? jql[0] : ""); + } + + /** + * @return All issues in the Board . + * @throws JiraException when the retrieval fails + */ + public List getIssues(String... jql) + throws JiraException + { + return AgileResource.list(getRestclient(), + Issue.class, + RESOURCE_URI + "board/" + getId() + "/issue", + "issues", + jql.length > 0 ? jql[0] : ""); } /** * @return All epics associated to the Board. * @throws JiraException when the retrieval fails */ - public List getEpics() throws JiraException { - return AgileResource.list(getRestclient(), Epic.class, RESOURCE_URI + "board/" + getId() + "/epic"); + public List getEpics(String... jql) + throws JiraException + { + return AgileResource.list(getRestclient(), + Epic.class, + RESOURCE_URI + "board/" + getId() + "/epic", + jql.length > 0 ? jql[0] : ""); } } - diff --git a/src/main/java/net/rcarz/jiraclient/agile/Epic.java b/src/main/java/net/rcarz/jiraclient/agile/Epic.java index 65315721..5f9ac82a 100644 --- a/src/main/java/net/rcarz/jiraclient/agile/Epic.java +++ b/src/main/java/net/rcarz/jiraclient/agile/Epic.java @@ -19,19 +19,20 @@ package net.rcarz.jiraclient.agile; +import java.util.List; + import net.rcarz.jiraclient.Field; import net.rcarz.jiraclient.JiraException; import net.rcarz.jiraclient.RestClient; import net.sf.json.JSONObject; -import java.util.List; - /** * Represents an Agile Epic. * * @author pldupont */ -public class Epic extends AgileResource { +public class Epic extends AgileResource +{ private Issue issue; private String key; @@ -42,9 +43,12 @@ public class Epic extends AgileResource { * Creates a new Agile resource. * * @param restclient REST client instance - * @param json JSON payload + * @param json JSON payload */ - public Epic(RestClient restclient, JSONObject json) throws JiraException { + public Epic(RestClient restclient, + JSONObject json) + throws JiraException + { super(restclient, json); } @@ -52,11 +56,14 @@ public Epic(RestClient restclient, JSONObject json) throws JiraException { * Retrieves the epic matching the ID. * * @param restclient REST client instance - * @param id Internal JIRA ID of the epic + * @param id Internal JIRA ID of the epic * @return an epic instance * @throws JiraException when the retrieval fails */ - public static Epic get(RestClient restclient, long id) throws JiraException { + public static Epic get(RestClient restclient, + long id) + throws JiraException + { return AgileResource.get(restclient, Epic.class, RESOURCE_URI + "epic/" + id); } @@ -65,8 +72,11 @@ public static Epic get(RestClient restclient, long id) throws JiraException { * @return The Issue representation of this Epic. * @throws JiraException when the retrieval fails */ - public Issue asIssue(boolean refresh) throws JiraException { - if (this.issue == null || refresh) { + public Issue asIssue(boolean refresh) + throws JiraException + { + if (this.issue == null || refresh) + { this.issue = Issue.get(getRestclient(), getId()); } return this.issue; @@ -76,8 +86,14 @@ public Issue asIssue(boolean refresh) throws JiraException { * @return All issues in the Epic. * @throws JiraException when the retrieval fails */ - public List getIssues() throws JiraException { - return AgileResource.list(getRestclient(), Issue.class, RESOURCE_URI + "epic/" + getId() + "/issue", "issues"); + public List getIssues(String... jql) + throws JiraException + { + return AgileResource.list(getRestclient(), + Issue.class, + RESOURCE_URI + "epic/" + getId() + "/issue", + "issues", + jql.length > 0 ? jql[0] : ""); } /** @@ -87,7 +103,9 @@ public List getIssues() throws JiraException { * @param json The JSON object to read. */ @Override - void deserialize(JSONObject json) throws JiraException { + void deserialize(JSONObject json) + throws JiraException + { super.deserialize(json); this.key = Field.getString(json.get("key")); @@ -95,15 +113,18 @@ void deserialize(JSONObject json) throws JiraException { this.done = Field.getBoolean(json.get("done")); } - public String getKey() { + public String getKey() + { return key; } - public String getSummary() { + public String getSummary() + { return summary; } - public boolean isDone() { + public boolean isDone() + { return done; } } diff --git a/src/main/java/net/rcarz/jiraclient/agile/Issue.java b/src/main/java/net/rcarz/jiraclient/agile/Issue.java index 5b2c4a8a..371cfe52 100644 --- a/src/main/java/net/rcarz/jiraclient/agile/Issue.java +++ b/src/main/java/net/rcarz/jiraclient/agile/Issue.java @@ -19,20 +19,21 @@ package net.rcarz.jiraclient.agile; +import java.util.Date; +import java.util.List; + import net.rcarz.jiraclient.Field; import net.rcarz.jiraclient.JiraException; import net.rcarz.jiraclient.RestClient; import net.sf.json.JSONObject; -import java.util.Date; -import java.util.List; - /** * Represents an Agile Issue. * * @author pldupont */ -public class Issue extends AgileResource { +public class Issue extends AgileResource +{ private String key; private boolean flagged; @@ -55,14 +56,16 @@ public class Issue extends AgileResource { private User reporter; private String environment; - /** * Creates a new Agile Issue resource. * * @param restclient REST client instance - * @param json JSON payload + * @param json JSON payload */ - public Issue(RestClient restclient, JSONObject json) throws JiraException { + public Issue(RestClient restclient, + JSONObject json) + throws JiraException + { super(restclient, json); } @@ -70,11 +73,14 @@ public Issue(RestClient restclient, JSONObject json) throws JiraException { * Retrieves the issue matching the ID. * * @param restclient REST client instance - * @param id Internal JIRA ID of the issue + * @param id Internal JIRA ID of the issue * @return an issue instance * @throws JiraException when the retrieval fails */ - public static Issue get(RestClient restclient, long id) throws JiraException { + public static Issue get(RestClient restclient, + long id) + throws JiraException + { return AgileResource.get(restclient, Issue.class, RESOURCE_URI + "issue/" + id); } @@ -82,21 +88,27 @@ public static Issue get(RestClient restclient, long id) throws JiraException { * Retrieves the issue matching the ID. * * @param restclient REST client instance - * @param key JIRA key of the issue + * @param key JIRA key of the issue * @return an issue instance * @throws JiraException when the retrieval fails */ - public static Issue get(RestClient restclient, String key) throws JiraException { + public static Issue get(RestClient restclient, + String key) + throws JiraException + { return AgileResource.get(restclient, Issue.class, RESOURCE_URI + "issue/" + key); } @Override - protected void deserialize(JSONObject json) throws JiraException { + protected void deserialize(JSONObject json) + throws JiraException + { super.deserialize(json); this.key = Field.getString(json.get("key")); // Extract from Field sub JSONObject - if (json.containsKey("fields")) { + if (json.containsKey("fields")) + { JSONObject fields = (JSONObject) json.get("fields"); setName(Field.getString(fields.get("summary"))); this.flagged = Field.getBoolean(fields.get("flagged")); @@ -123,83 +135,103 @@ protected void deserialize(JSONObject json) throws JiraException { } } - public String getKey() { + public String getKey() + { return key; } - public boolean isFlagged() { + public boolean isFlagged() + { return flagged; } - public Sprint getSprint() { + public Sprint getSprint() + { return sprint; } - public List getClosedSprints() { + public List getClosedSprints() + { return closedSprints; } - public String getDescription() { + public String getDescription() + { return description; } - public Project getProject() { + public Project getProject() + { return project; } - public List getComments() { + public List getComments() + { return comments; } - public Epic getEpic() { + public Epic getEpic() + { return epic; } - public List getWorklogs() { + public List getWorklogs() + { return worklogs; } - public TimeTracking getTimeTracking() { + public TimeTracking getTimeTracking() + { return timeTracking; } - public IssueType getIssueType() { + public IssueType getIssueType() + { return issueType; } - public Status getStatus() { + public Status getStatus() + { return status; } - public Resolution getResolution() { + public Resolution getResolution() + { return resolution; } - public Date getCreated() { + public Date getCreated() + { return created; } - public Date getUpdated() { + public Date getUpdated() + { return updated; } - public Priority getPriority() { + public Priority getPriority() + { return priority; } - public User getAssignee() { + public User getAssignee() + { return assignee; } - public User getCreator() { + public User getCreator() + { return creator; } - public User getReporter() { + public User getReporter() + { return reporter; } - public String getEnvironment() { + public String getEnvironment() + { return environment; } } From c425ee21066f9216f25cc0e4ce3d14b2c82ecd1b Mon Sep 17 00:00:00 2001 From: Daniel Stutz Date: Thu, 18 Jan 2018 16:13:56 +0100 Subject: [PATCH 3/5] Get rid of diamond operator to support Java 1.5 --- src/main/java/net/rcarz/jiraclient/agile/AgileResource.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/rcarz/jiraclient/agile/AgileResource.java b/src/main/java/net/rcarz/jiraclient/agile/AgileResource.java index 305d8d95..318f9342 100644 --- a/src/main/java/net/rcarz/jiraclient/agile/AgileResource.java +++ b/src/main/java/net/rcarz/jiraclient/agile/AgileResource.java @@ -187,10 +187,10 @@ static List list(RestClient restclient, throws JiraException { - final ResourceIterator iter = + final ResourceIterator iter = new ResourceIterator(restclient, type, url, listName, jql, null, null, null, null); - List list = new ArrayList<>(); + List list = new ArrayList(); while (iter.hasNext()) { list.add((T) iter.next()); From 35b1c4ef4628fa86e220e71b255b8933632e95ee Mon Sep 17 00:00:00 2001 From: Daniel Stutz Date: Thu, 18 Jan 2018 16:17:25 +0100 Subject: [PATCH 4/5] Get rid of diamond operator to support Java 1.5 --- src/main/java/net/rcarz/jiraclient/agile/ResourceIterator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/rcarz/jiraclient/agile/ResourceIterator.java b/src/main/java/net/rcarz/jiraclient/agile/ResourceIterator.java index 51ffcf5d..6de5dac6 100644 --- a/src/main/java/net/rcarz/jiraclient/agile/ResourceIterator.java +++ b/src/main/java/net/rcarz/jiraclient/agile/ResourceIterator.java @@ -159,7 +159,7 @@ else if (issues != null) } if (isLast || (startAt != 0 && total != 0 && startAt >= total)) - return new ArrayList<>(); + return new ArrayList(); JSON result = null; try From 62be61e7f6fcda956b52213d9750ab8a6d6f5fd3 Mon Sep 17 00:00:00 2001 From: Daniel Stutz Date: Wed, 9 Jan 2019 13:14:02 +0100 Subject: [PATCH 5/5] Allow to request which fields should be included in the result set objects. --- .../java/net/rcarz/jiraclient/IssueType.java | 46 +++++--- .../rcarz/jiraclient/agile/AgileResource.java | 102 +++++++++++++++++- .../net/rcarz/jiraclient/agile/Board.java | 24 ++--- .../java/net/rcarz/jiraclient/agile/Epic.java | 5 +- .../jiraclient/agile/ResourceIterator.java | 12 ++- .../net/rcarz/jiraclient/agile/Sprint.java | 74 +++++++++---- 6 files changed, 202 insertions(+), 61 deletions(-) diff --git a/src/main/java/net/rcarz/jiraclient/IssueType.java b/src/main/java/net/rcarz/jiraclient/IssueType.java index 0359cfd8..92d6975f 100644 --- a/src/main/java/net/rcarz/jiraclient/IssueType.java +++ b/src/main/java/net/rcarz/jiraclient/IssueType.java @@ -27,7 +27,8 @@ /** * Represents an issue type. */ -public class IssueType extends Resource { +public class IssueType extends Resource +{ private String description = null; private String iconUrl = null; @@ -41,14 +42,17 @@ public class IssueType extends Resource { * @param restclient REST client instance * @param json JSON payload */ - protected IssueType(RestClient restclient, JSONObject json) { + protected IssueType(RestClient restclient, + JSONObject json) + { super(restclient); if (json != null) deserialise(json); } - private void deserialise(JSONObject json) { + private void deserialise(JSONObject json) + { Map map = json; self = Field.getString(map.get("self")); @@ -59,7 +63,7 @@ private void deserialise(JSONObject json) { subtask = Field.getBoolean(map.get("subtask")); if (map.containsKey("fields") && map.get("fields") instanceof JSONObject) - fields = (JSONObject)map.get("fields"); + fields = (JSONObject) map.get("fields"); } /** @@ -72,46 +76,56 @@ private void deserialise(JSONObject json) { * * @throws JiraException when the retrieval fails */ - public static IssueType get(RestClient restclient, String id) - throws JiraException { + public static IssueType get(RestClient restclient, + String id) + throws JiraException + { JSON result = null; - try { + try + { result = restclient.get(getBaseUri() + "issuetype/" + id); - } catch (Exception ex) { + } + catch (Exception ex) + { throw new JiraException("Failed to retrieve issue type " + id, ex); } if (!(result instanceof JSONObject)) throw new JiraException("JSON payload is malformed"); - return new IssueType(restclient, (JSONObject)result); + return new IssueType(restclient, (JSONObject) result); } @Override - public String toString() { + public String toString() + { return getName(); } - public String getDescription() { + public String getDescription() + { return description; } - public String getName() { + public String getName() + { return name; } - public String getIconUrl() { + public String getIconUrl() + { return iconUrl; } - public boolean isSubtask() { + public boolean isSubtask() + { return subtask; } - public JSONObject getFields() { + public JSONObject getFields() + { return fields; } } - diff --git a/src/main/java/net/rcarz/jiraclient/agile/AgileResource.java b/src/main/java/net/rcarz/jiraclient/agile/AgileResource.java index 318f9342..b8eef289 100644 --- a/src/main/java/net/rcarz/jiraclient/agile/AgileResource.java +++ b/src/main/java/net/rcarz/jiraclient/agile/AgileResource.java @@ -71,6 +71,38 @@ public AgileResource(RestClient restclient, } } + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + (int) (id ^ (id >>> 32)); + result = prime * result + ((self == null) ? 0 : self.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AgileResource other = (AgileResource) obj; + if (id != other.id) + return false; + if (self == null) + { + if (other.self != null) + return false; + } + else if (!self.equals(other.self)) + return false; + return true; + } + /** * Gets an Agile resource from the given object. * @@ -151,6 +183,23 @@ protected static List getResourceArray(Class typ return results; } + /** + * Retrieves all boards visible to the session user. + * + * @param restclient REST client instance + * @param type The type of the object to deserialize. + * @param url The URL to call. + * @return a list of boards + * @throws JiraException when the retrieval fails + */ + static List list(RestClient restclient, + Class type, + String url) + throws JiraException + { + return list(restclient, type, url, "values", null, null, null); + } + /** * Retrieves all boards visible to the session user. * @@ -163,10 +212,10 @@ protected static List getResourceArray(Class typ static List list(RestClient restclient, Class type, String url, - String... jql) + String listName) throws JiraException { - return list(restclient, type, url, "values", jql.length > 0 ? jql[0] : ""); + return list(restclient, type, url, listName, null, null, null); } /** @@ -175,7 +224,6 @@ static List list(RestClient restclient, * @param restclient REST client instance * @param type The type of the object to deserialize. * @param url The URL to call. - * @param listName The name of the list of items in the JSON response. * @return a list of boards * @throws JiraException when the retrieval fails */ @@ -186,9 +234,51 @@ static List list(RestClient restclient, String jql) throws JiraException { + return list(restclient, type, url, listName, jql, null, null); + } + + /** + * Retrieves all boards visible to the session user. + * + * @param restclient REST client instance + * @param type The type of the object to deserialize. + * @param url The URL to call. + * @return a list of boards + * @throws JiraException when the retrieval fails + */ + static List list(RestClient restclient, + Class type, + String url, + String listName, + String jql, + String includedFields) + throws JiraException + { + return list(restclient, type, url, listName, jql, includedFields, null); + } + + /** + * Retrieves all boards visible to the session user. + * + * @param restclient REST client instance + * @param type The type of the object to deserialize. + * @param url The URL to call. + * @param listName The name of the list of items in the JSON response. + * @return a list of boards + * @throws JiraException when the retrieval fails + */ + static List list(RestClient restclient, + Class type, + String url, + String listName, + String jql, + String includedFields, + String expandFields) + throws JiraException + { final ResourceIterator iter = - new ResourceIterator(restclient, type, url, listName, jql, null, null, null, null); + new ResourceIterator(restclient, type, url, listName, jql, includedFields, expandFields, null, null); List list = new ArrayList(); while (iter.hasNext()) @@ -240,7 +330,9 @@ List getSubResourceArray(Class type, throws JiraException { List result = null; - if (subJson.containsKey(resourceName)) + if (subJson.containsKey(resourceName) + && !subJson.get(resourceName) + .equals("null")) { result = getResourceArray(type, subJson.get(resourceName), getRestclient(), resourceName + "s"); } diff --git a/src/main/java/net/rcarz/jiraclient/agile/Board.java b/src/main/java/net/rcarz/jiraclient/agile/Board.java index 301f354a..0b5c28d2 100644 --- a/src/main/java/net/rcarz/jiraclient/agile/Board.java +++ b/src/main/java/net/rcarz/jiraclient/agile/Board.java @@ -71,11 +71,10 @@ public static Board get(RestClient restclient, * @return a list of boards * @throws JiraException when the retrieval fails */ - public static List getAll(RestClient restclient, - String... jql) + public static List getAll(RestClient restclient) throws JiraException { - return AgileResource.list(restclient, Board.class, RESOURCE_URI + "board", jql.length > 0 ? jql[0] : ""); + return AgileResource.list(restclient, Board.class, RESOURCE_URI + "board"); } @Override @@ -121,40 +120,37 @@ public List getBacklog() * @return All issues without epic in the Board . * @throws JiraException when the retrieval fails */ - public List getIssuesWithoutEpic(String... jql) + public List getIssuesWithoutEpic() throws JiraException { return AgileResource.list(getRestclient(), Issue.class, RESOURCE_URI + "board/" + getId() + "/epic/none/issue", - "issues", - jql.length > 0 ? jql[0] : ""); + "issues"); } /** * @return All issues in the Board . * @throws JiraException when the retrieval fails */ - public List getIssues(String... jql) + public List getIssues() throws JiraException { - return AgileResource.list(getRestclient(), - Issue.class, - RESOURCE_URI + "board/" + getId() + "/issue", - "issues", - jql.length > 0 ? jql[0] : ""); + return AgileResource.list(getRestclient(), Issue.class, RESOURCE_URI + "board/" + getId() + "/issue", "issues"); } /** * @return All epics associated to the Board. * @throws JiraException when the retrieval fails */ - public List getEpics(String... jql) + public List getEpics(final String fields) throws JiraException { return AgileResource.list(getRestclient(), Epic.class, RESOURCE_URI + "board/" + getId() + "/epic", - jql.length > 0 ? jql[0] : ""); + "values", + null, + fields); } } diff --git a/src/main/java/net/rcarz/jiraclient/agile/Epic.java b/src/main/java/net/rcarz/jiraclient/agile/Epic.java index 5f9ac82a..fceeb25f 100644 --- a/src/main/java/net/rcarz/jiraclient/agile/Epic.java +++ b/src/main/java/net/rcarz/jiraclient/agile/Epic.java @@ -86,14 +86,15 @@ public Issue asIssue(boolean refresh) * @return All issues in the Epic. * @throws JiraException when the retrieval fails */ - public List getIssues(String... jql) + public List getIssues(final String fields) throws JiraException { return AgileResource.list(getRestclient(), Issue.class, RESOURCE_URI + "epic/" + getId() + "/issue", "issues", - jql.length > 0 ? jql[0] : ""); + "", + fields); } /** diff --git a/src/main/java/net/rcarz/jiraclient/agile/ResourceIterator.java b/src/main/java/net/rcarz/jiraclient/agile/ResourceIterator.java index 6de5dac6..3acc7582 100644 --- a/src/main/java/net/rcarz/jiraclient/agile/ResourceIterator.java +++ b/src/main/java/net/rcarz/jiraclient/agile/ResourceIterator.java @@ -54,7 +54,7 @@ public ResourceIterator(RestClient restclient, this.jql = jql; this.includedFields = includedFields; this.expandFields = expandFields; - this.maxResults = (maxResults == null ? 500 : maxResults); + this.maxResults = (maxResults == null ? 100 : maxResults); this.startAt = startAt; } @@ -189,16 +189,19 @@ private URI createURI() throws URISyntaxException { Map queryParams = new HashMap(); - queryParams.put("jql", jql); + if (jql != null && !jql.isEmpty()) + { + queryParams.put("jql", jql); + } if (maxResults != null) { queryParams.put("maxResults", String.valueOf(maxResults)); } - if (includedFields != null) + if (includedFields != null && !includedFields.isEmpty()) { queryParams.put("fields", includedFields); } - if (expandFields != null) + if (expandFields != null && !expandFields.isEmpty()) { queryParams.put("expand", expandFields); } @@ -208,7 +211,6 @@ private URI createURI() } URI uri = restclient.buildURI(url, queryParams); - // System.out.println(uri); return uri; } } diff --git a/src/main/java/net/rcarz/jiraclient/agile/Sprint.java b/src/main/java/net/rcarz/jiraclient/agile/Sprint.java index ac20f63d..35d10013 100644 --- a/src/main/java/net/rcarz/jiraclient/agile/Sprint.java +++ b/src/main/java/net/rcarz/jiraclient/agile/Sprint.java @@ -19,20 +19,21 @@ package net.rcarz.jiraclient.agile; +import java.util.Date; +import java.util.List; + import net.rcarz.jiraclient.Field; import net.rcarz.jiraclient.JiraException; import net.rcarz.jiraclient.RestClient; import net.sf.json.JSONObject; -import java.util.Date; -import java.util.List; - /** * Represents an Agile Sprint. * * @author pldupont */ -public class Sprint extends AgileResource { +public class Sprint extends AgileResource +{ private String state; private long originBoardId; @@ -44,9 +45,12 @@ public class Sprint extends AgileResource { * Creates a rapid view from a JSON payload. * * @param restclient REST client instance - * @param json JSON payload + * @param json JSON payload */ - protected Sprint(RestClient restclient, JSONObject json) throws JiraException { + protected Sprint(RestClient restclient, + JSONObject json) + throws JiraException + { super(restclient, json); } @@ -54,11 +58,14 @@ protected Sprint(RestClient restclient, JSONObject json) throws JiraException { * Retrieve all sprints related to the specified board. * * @param restclient REST client instance - * @param sprintId The Internal JIRA sprint ID. + * @param sprintId The Internal JIRA sprint ID. * @return The sprint for the specified ID. * @throws JiraException when the retrieval fails */ - public static Sprint get(RestClient restclient, long sprintId) throws JiraException { + public static Sprint get(RestClient restclient, + long sprintId) + throws JiraException + { return AgileResource.get(restclient, Sprint.class, RESOURCE_URI + "sprint/" + sprintId); } @@ -66,11 +73,14 @@ public static Sprint get(RestClient restclient, long sprintId) throws JiraExcept * Retrieve all sprints related to the specified board. * * @param restclient REST client instance - * @param boardId The Internal JIRA board ID. + * @param boardId The Internal JIRA board ID. * @return The list of sprints associated to the board. * @throws JiraException when the retrieval fails */ - public static List getAll(RestClient restclient, long boardId) throws JiraException { + public static List getAll(RestClient restclient, + long boardId) + throws JiraException + { return AgileResource.list(restclient, Sprint.class, RESOURCE_URI + "board/" + boardId + "/sprint"); } @@ -78,12 +88,34 @@ public static List getAll(RestClient restclient, long boardId) throws Ji * @return All issues in the Sprint. * @throws JiraException when the retrieval fails */ - public List getIssues() throws JiraException { - return AgileResource.list(getRestclient(), Issue.class, RESOURCE_URI + "sprint/" + getId() + "/issue", "issues"); + public List getIssues() + throws JiraException + { + return AgileResource.list(getRestclient(), + Issue.class, + RESOURCE_URI + "sprint/" + getId() + "/issue", + "issues"); + } + + /** + * @return All issues in the Sprint. + * @throws JiraException when the retrieval fails + */ + public List getIssues(final String fields) + throws JiraException + { + return AgileResource.list(getRestclient(), + Issue.class, + RESOURCE_URI + "sprint/" + getId() + "/issue", + "issues", + "", + fields); } @Override - protected void deserialize(JSONObject json) throws JiraException { + protected void deserialize(JSONObject json) + throws JiraException + { super.deserialize(json); state = Field.getString(json.get("state")); originBoardId = getLong(json.get("originBoardId")); @@ -92,24 +124,28 @@ protected void deserialize(JSONObject json) throws JiraException { completeDate = Field.getDateTime(json.get("completeDate")); } - public String getState() { + public String getState() + { return state; } - public long getOriginBoardId() { + public long getOriginBoardId() + { return originBoardId; } - public Date getStartDate() { + public Date getStartDate() + { return startDate; } - public Date getEndDate() { + public Date getEndDate() + { return endDate; } - public Date getCompleteDate() { + public Date getCompleteDate() + { return completeDate; } } -