diff --git a/src/main/java/com/taskadapter/redmineapi/IssueManager.java b/src/main/java/com/taskadapter/redmineapi/IssueManager.java index fc7538be..4c453fa7 100644 --- a/src/main/java/com/taskadapter/redmineapi/IssueManager.java +++ b/src/main/java/com/taskadapter/redmineapi/IssueManager.java @@ -68,10 +68,9 @@ public List getIssuesBySummary(String projectKey, String summaryField) th } /** - * Generic method to search for issues. - *

Note that you cannot currently use "offset" and "limit" parameters because - * paging is managed by Transport class internally. This method will always return all found objects - * even if it had to perform multiple requests to the server to load several pages. + * Direct method to search for issues using any Redmine REST API parameters you want. + *

Unlike other getXXXObjects() methods in this library, this one does NOT handle paging for you so + * you have to provide "offset" and "limit" parameters if you want to control paging. * * @param pParameters the http parameters key/value pairs to append to the rest api request * @return empty list if no issues found matching given parameters @@ -86,7 +85,8 @@ public List getIssues(Map pParameters) throws RedmineExce params.add(new BasicNameValuePair(param.getKey(), param.getValue())); } - return transport.getObjectsList(Issue.class, params); + final Transport.ResultsWrapper wrapper = transport.getObjectsListNoPaging(Issue.class, params); + return wrapper.getResults(); } /** diff --git a/src/main/java/com/taskadapter/redmineapi/internal/Transport.java b/src/main/java/com/taskadapter/redmineapi/internal/Transport.java index c18aa345..6cc1f12f 100644 --- a/src/main/java/com/taskadapter/redmineapi/internal/Transport.java +++ b/src/main/java/com/taskadapter/redmineapi/internal/Transport.java @@ -394,63 +394,91 @@ public List getObjectsList(Class objectClass, NameValuePair... params) } /** - * Returns an object list. + * Returns all objects found using the provided parameters. + * This method IGNORES "limit" and "offset" parameters and handles paging AUTOMATICALLY for you. + * Please use getObjectsListNoPaging() method if you want to control paging yourself with "limit" and "offset" parameters. * * @return objects list, never NULL + * + * @see #getObjectsListNoPaging(Class, Collection) */ public List getObjectsList(Class objectClass, - Collection params) throws RedmineException { - final EntityConfig config = getConfig(objectClass); + Collection params) throws RedmineException { final List result = new ArrayList(); - - final List newParams = new ArrayList(params); - - newParams.add(new BasicNameValuePair("limit", String.valueOf(objectsPerPage))); int offset = 0; - int totalObjectsFoundOnServer; + Integer totalObjectsFoundOnServer; do { - List paramsList = new ArrayList(newParams); - paramsList.add(new BasicNameValuePair("offset", String.valueOf(offset))); - - final URI uri = getURIConfigurator().getObjectsURI(objectClass, paramsList); - - logger.debug(uri.toString()); - final HttpGet http = new HttpGet(uri); - final String response = send(http); - logger.debug("received: " + response); - - final List foundItems; - try { - final JSONObject responseObject = RedmineJSONParser.getResponse(response); - foundItems = JsonInput.getListOrNull(responseObject,config.multiObjectName, config.parser); - result.addAll(foundItems); - - /* Necessary for trackers */ - if (!responseObject.has(KEY_TOTAL_COUNT)) { - break; - } - totalObjectsFoundOnServer = JsonInput.getInt(responseObject,KEY_TOTAL_COUNT); - } catch (JSONException e) { - throw new RedmineFormatException(e); - } + final List newParams = new ArrayList(params); + newParams.add(new BasicNameValuePair("limit", String.valueOf(objectsPerPage))); + newParams.add(new BasicNameValuePair("offset", String.valueOf(offset))); + + final ResultsWrapper wrapper = getObjectsListNoPaging(objectClass, newParams); + result.addAll(wrapper.getResults()); - if (foundItems.size() == 0) { + totalObjectsFoundOnServer = wrapper.getTotalFoundOnServer(); + // Necessary for trackers. + // TODO Alexey: is this still necessary for Redmine 2.x? + if (totalObjectsFoundOnServer == null) { break; } - - offset += foundItems.size(); + if (!wrapper.hasSomeResults()) { + break; + } + offset += wrapper.getResultsNumber(); } while (offset < totalObjectsFoundOnServer); - return result; } /** - * This number of objects (tasks, projects, users) will be requested from - * Redmine server in 1 request. + * Returns an object list. Provide your own "limit" and "offset" parameters if you need those, otherwise + * this method will return the first page of some default size only (this default is controlled by + * your Redmine configuration). + * + * @return objects list, never NULL */ - public int getObjectsPerPage() { - return objectsPerPage; + public ResultsWrapper getObjectsListNoPaging(Class objectClass, + Collection params) throws RedmineException { + final EntityConfig config = getConfig(objectClass); + final List newParams = new ArrayList(params); + List paramsList = new ArrayList(newParams); + final URI uri = getURIConfigurator().getObjectsURI(objectClass, paramsList); + final HttpGet http = new HttpGet(uri); + final String response = send(http); + try { + final JSONObject responseObject = RedmineJSONParser.getResponse(response); + List results = JsonInput.getListOrNull(responseObject, config.multiObjectName, config.parser); + Integer totalFoundOnServer = JsonInput.getIntOrNull(responseObject, KEY_TOTAL_COUNT); + return new ResultsWrapper(totalFoundOnServer, results); + } catch (JSONException e) { + throw new RedmineFormatException(e); + } + } + + public static class ResultsWrapper { + final private Integer totalFoundOnServer; + final private List results; + + public ResultsWrapper(Integer totalFoundOnServer, List results) { + this.totalFoundOnServer = totalFoundOnServer; + this.results = results; + } + + public boolean hasSomeResults() { + return !results.isEmpty(); + } + + public List getResults() { + return results; + } + + public int getResultsNumber() { + return results.size(); + } + + public Integer getTotalFoundOnServer() { + return totalFoundOnServer; + } } public List getChildEntries(Class parentClass, int parentId, Class classs) throws RedmineException { diff --git a/src/test/java/com/taskadapter/redmineapi/IssueManagerTest.java b/src/test/java/com/taskadapter/redmineapi/IssueManagerTest.java index 9891d14e..d0d9180f 100644 --- a/src/test/java/com/taskadapter/redmineapi/IssueManagerTest.java +++ b/src/test/java/com/taskadapter/redmineapi/IssueManagerTest.java @@ -33,8 +33,10 @@ import java.util.Calendar; import java.util.Collection; import java.util.Date; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.UUID; @@ -302,10 +304,24 @@ public void testGetIssuesPaging() throws RedmineException { // create 27 issues. default page size is 25. createIssues(issueManager, projectId, 27); List issues = issueManager.getIssues(projectKey, null); - assertTrue(issues.size() > 26); + assertThat(issues.size()).isGreaterThan(26); + // check that there are no duplicates in the list. Set issueSet = new HashSet(issues); - assertEquals(issues.size(), issueSet.size()); + assertThat(issueSet.size()).isEqualTo(issues.size()); + } + + @Test + public void canControlLimitAndOffsetDirectly() throws RedmineException { + // create 27 issues. default Redmine page size is usually 25 (unless changed in the server settings). + createIssues(issueManager, projectId, 27); + Map params = new HashMap(); + params.put("limit", "3"); + params.put("offset", "0"); + params.put("project_id", projectId + ""); + List issues = issueManager.getIssues(params); + // only the requested number of issues is loaded, not all result pages. + assertThat(issues.size()).isEqualTo(3); } @Test(expected = NotFoundException.class)