Skip to content

Commit

Permalink
Merge pull request #188 from taskadapter/ask-140-limit-offset
Browse files Browse the repository at this point in the history
Issue #114. breaking change! getIssues(params map) method in IssueManage...
  • Loading branch information
alexeyOnGitHub committed Mar 25, 2015
2 parents 86d4263 + 82d29bb commit dc66ba5
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 47 deletions.
10 changes: 5 additions & 5 deletions src/main/java/com/taskadapter/redmineapi/IssueManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,9 @@ public List<Issue> getIssuesBySummary(String projectKey, String summaryField) th
}

/**
* Generic method to search for issues.
* <p>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.
* <p>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
Expand All @@ -86,7 +85,8 @@ public List<Issue> getIssues(Map<String, String> pParameters) throws RedmineExce
params.add(new BasicNameValuePair(param.getKey(), param.getValue()));
}

return transport.getObjectsList(Issue.class, params);
final Transport.ResultsWrapper<Issue> wrapper = transport.getObjectsListNoPaging(Issue.class, params);
return wrapper.getResults();
}

/**
Expand Down
108 changes: 68 additions & 40 deletions src/main/java/com/taskadapter/redmineapi/internal/Transport.java
Original file line number Diff line number Diff line change
Expand Up @@ -394,63 +394,91 @@ public <T> List<T> getObjectsList(Class<T> 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 <T> List<T> getObjectsList(Class<T> objectClass,
Collection<? extends NameValuePair> params) throws RedmineException {
final EntityConfig<T> config = getConfig(objectClass);
Collection<? extends NameValuePair> params) throws RedmineException {
final List<T> result = new ArrayList<T>();

final List<NameValuePair> newParams = new ArrayList<NameValuePair>(params);

newParams.add(new BasicNameValuePair("limit", String.valueOf(objectsPerPage)));
int offset = 0;

int totalObjectsFoundOnServer;
Integer totalObjectsFoundOnServer;
do {
List<NameValuePair> paramsList = new ArrayList<NameValuePair>(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<T> 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<NameValuePair> newParams = new ArrayList<NameValuePair>(params);
newParams.add(new BasicNameValuePair("limit", String.valueOf(objectsPerPage)));
newParams.add(new BasicNameValuePair("offset", String.valueOf(offset)));

final ResultsWrapper<T> 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 <T> ResultsWrapper<T> getObjectsListNoPaging(Class<T> objectClass,
Collection<? extends NameValuePair> params) throws RedmineException {
final EntityConfig<T> config = getConfig(objectClass);
final List<NameValuePair> newParams = new ArrayList<NameValuePair>(params);
List<NameValuePair> paramsList = new ArrayList<NameValuePair>(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<T> results = JsonInput.getListOrNull(responseObject, config.multiObjectName, config.parser);
Integer totalFoundOnServer = JsonInput.getIntOrNull(responseObject, KEY_TOTAL_COUNT);
return new ResultsWrapper<T>(totalFoundOnServer, results);
} catch (JSONException e) {
throw new RedmineFormatException(e);
}
}

public static class ResultsWrapper<T> {
final private Integer totalFoundOnServer;
final private List<T> results;

public ResultsWrapper(Integer totalFoundOnServer, List<T> results) {
this.totalFoundOnServer = totalFoundOnServer;
this.results = results;
}

public boolean hasSomeResults() {
return !results.isEmpty();
}

public List<T> getResults() {
return results;
}

public int getResultsNumber() {
return results.size();
}

public Integer getTotalFoundOnServer() {
return totalFoundOnServer;
}
}

public <T> List<T> getChildEntries(Class<?> parentClass, int parentId, Class<T> classs) throws RedmineException {
Expand Down
20 changes: 18 additions & 2 deletions src/test/java/com/taskadapter/redmineapi/IssueManagerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -302,10 +304,24 @@ public void testGetIssuesPaging() throws RedmineException {
// create 27 issues. default page size is 25.
createIssues(issueManager, projectId, 27);
List<Issue> issues = issueManager.getIssues(projectKey, null);
assertTrue(issues.size() > 26);
assertThat(issues.size()).isGreaterThan(26);

// check that there are no duplicates in the list.
Set<Issue> issueSet = new HashSet<Issue>(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<String, String> params = new HashMap<String, String>();
params.put("limit", "3");
params.put("offset", "0");
params.put("project_id", projectId + "");
List<Issue> 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)
Expand Down

0 comments on commit dc66ba5

Please sign in to comment.