From d5cebb3e09fd6b9d7511697eff67c200aa839efc Mon Sep 17 00:00:00 2001 From: Greg Messner Date: Sun, 18 Jun 2017 17:36:15 -0700 Subject: [PATCH] Initial check-in. --- src/main/java/org/gitlab4j/api/Pager.java | 267 ++++++++++++++++++ src/test/java/org/gitlab4j/api/TestPager.java | 136 +++++++++ 2 files changed, 403 insertions(+) create mode 100644 src/main/java/org/gitlab4j/api/Pager.java create mode 100644 src/test/java/org/gitlab4j/api/TestPager.java diff --git a/src/main/java/org/gitlab4j/api/Pager.java b/src/main/java/org/gitlab4j/api/Pager.java new file mode 100644 index 00000000..30a06bdd --- /dev/null +++ b/src/main/java/org/gitlab4j/api/Pager.java @@ -0,0 +1,267 @@ +package org.gitlab4j.api; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; + +import org.gitlab4j.api.utils.JacksonJson; + +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * This class defines an Iterator implementation that is used as a paging iterator for all API methods that + * return a List of objects. It hides the details of interacting with the GitLab API when paging is involved + * simplifying accessing large lists of objects. + * + *

Example usage:

+ * + *
+ *   // Get a Pager instance that will page through the projects with 10 projects per page
+ *   Pager<Project> projectPager = gitlabApi.getProjectsApi().getProjectsPager(10);
+ *
+ *   // Iterate through the pages and print out the name and description
+ *   while (projectsPager.hasNext())) {
+ *       List<Project> projects = projectsPager.next();
+ *       for (Project project : projects) {
+ *           System.out.println(project.getName() + " -: " + project.getDescription());
+ *       }
+ *   }
+ * 
+ * + * @param the GitLab4J type contained in the List. + */ +public class Pager implements Iterator>, Constants { + + private int itemsPerPage; + private int totalPages; + private int totalItems; + private int currentPage; + + private List pageParam = new ArrayList<>(1); + private List currentItems; + + private AbstractApi api; + private MultivaluedMap queryParams; + private Object[] pathArgs; + + private static JacksonJson jacksonJson = new JacksonJson(); + private static ObjectMapper mapper = jacksonJson.getObjectMapper(); + private JavaType javaType; + + /** + * Creates a Pager instance to access the API through the specified path and query parameters. + * + * @param api the AbstractApi implementation to communicate through + * @param type the GitLab4J type that will be contained in the List + * @param itemsPerPage items per page + * @param queryParams HTTP query params + * @param pathArgs HTTP path arguments + * @throws GitLabApiException if any error occurs + */ + Pager(AbstractApi api, Class type, int itemsPerPage, MultivaluedMap queryParams, Object... pathArgs) throws GitLabApiException { + + javaType = mapper.getTypeFactory().constructCollectionType(List.class, type); + + // Make sure the per_page parameter is present + if (queryParams == null) { + queryParams = new GitLabApiForm().withParam(PER_PAGE_PARAM, itemsPerPage).asMap(); + } else { + queryParams.remove(PER_PAGE_PARAM); + queryParams.add(PER_PAGE_PARAM, Integer.toString(itemsPerPage)); + } + + // Set the page param to 1 + pageParam = new ArrayList<>(); + pageParam.add("1"); + queryParams.put(PAGE_PARAM, pageParam); + Response response = api.get(Response.Status.OK, queryParams, pathArgs); + + try { + currentItems = mapper.readValue((InputStream) response.getEntity(), javaType); + } catch (IOException e) { + throw new GitLabApiException(e); + } + + this.api = api; + this.queryParams = queryParams; + this.pathArgs = pathArgs; + this.itemsPerPage = getHeaderValue(response, PER_PAGE); + totalPages = getHeaderValue(response, TOTAL_PAGES_HEADER); + totalItems = getHeaderValue(response, TOTAL_HEADER); + } + + /** + * Get the specified integer header value from the Response instance. + * + * @param response the Response instance to get the value from + * @param key the HTTP header key to get the value for + * @return the specified integer header value from the Response instance + * @throws GitLabApiException if any error occurs + */ + private int getHeaderValue(Response response, String key) throws GitLabApiException { + + String value = response.getHeaderString(key); + value = (value != null ? value.trim() : null); + if (value == null || value.length() == 0) + throw new GitLabApiException("Missing '" + key + "' header from server"); + + try { + return (Integer.parseInt(value)); + } catch (NumberFormatException nfe) { + throw new GitLabApiException("Invalid '" + key + "' header value (" + value + ") from server"); + } + } + + /** + * Sets the "page" query parameter. + * + * @param page the value for the "page" query parameter + */ + private void setPageParam(int page) { + pageParam.set(0, Integer.toString(page)); + queryParams.put(PAGE_PARAM, pageParam); + } + + /** + * Get the items per page value. + * + * @return the items per page value + */ + public int getItemsPerPage() { + return (itemsPerPage); + } + + /** + * Get the total number of pages returned by the GitLab API. + * + * @return the total number of pages returned by the GitLab API + */ + public int getTotalPages() { + return (totalPages); + } + + /** + * Get the total number of items (T instances) returned by the GitLab API. + * + * @return the total number of items (T instances) returned by the GitLab API + */ + public int getTotalItems() { + return (totalItems); + } + + /** + * Get the current page of the iteration. + * + * @return the current page of the iteration + */ + public int getCurrentPage() { + return (currentPage); + } + + /** + * Returns the true if there are additional pages to iterate over, otherwise returns false. + * + * @return true if there are additional pages to iterate over, otherwise returns false + */ + @Override + public boolean hasNext() { + return (currentPage < totalPages); + } + + /** + * Returns the next List in the iteration containing the next page of objects. + * + * @return the next List in the iteration + * @throws NoSuchElementException if the iteration has no more elements + * @throws RuntimeException if a GitLab API error occurs, will contain a wrapped GitLabApiException with the details of the error + */ + @Override + public List next() { + return (page(currentPage + 1)); + } + + /** + * Returns the first page of List. Will rewind the iterator. + * + * @return the first page of List + * @throws GitLabApiException if any error occurs + */ + public List first() throws GitLabApiException { + return (page(1)); + } + + /** + * Returns the last page of List. Will set the iterator to the end. + * + * @return the last page of List + * @throws GitLabApiException if any error occurs + */ + public List last() throws GitLabApiException { + return (page(totalPages)); + } + + /** + * Returns the previous page of List. Will set the iterator to the previous page. + * + * @return the previous page of List + * @throws GitLabApiException if any error occurs + */ + public List previous() throws GitLabApiException { + return (page(currentPage - 1)); + } + + /** + * Returns the current page of List. + * + * @return the current page of List + * @throws GitLabApiException if any error occurs + */ + public List current() throws GitLabApiException { + return (page(currentPage)); + } + + /** + * Returns the specified page of List. + * + * @param pageNumber the page to get + * @return the specified page of List + * @throws NoSuchElementException if the iteration has no more elements + * @throws RuntimeException if a GitLab API error occurs, will contain a wrapped GitLabApiException with the details of the error + */ + public List page(int pageNumber) { + + if (pageNumber > totalPages) { + throw new NoSuchElementException(); + } else if (pageNumber < 1) { + throw new NoSuchElementException(); + } + + if (currentPage == 0 && pageNumber == 1) { + currentPage = 1; + return (currentItems); + } + + if (currentPage == pageNumber) { + return (currentItems); + } + + try { + + setPageParam(pageNumber); + Response response = api.get(Response.Status.OK, queryParams, pathArgs); + currentItems = mapper.readValue((InputStream) response.getEntity(), javaType); + currentPage = pageNumber; + return (currentItems); + + } catch (GitLabApiException | IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/org/gitlab4j/api/TestPager.java b/src/test/java/org/gitlab4j/api/TestPager.java new file mode 100644 index 00000000..a995ce42 --- /dev/null +++ b/src/test/java/org/gitlab4j/api/TestPager.java @@ -0,0 +1,136 @@ +package org.gitlab4j.api; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import java.util.List; + +import org.gitlab4j.api.GitLabApi.ApiVersion; +import org.gitlab4j.api.models.Project; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +/** + * In order for these tests to run you must set the following systems properties: + * + * TEST_NAMESPACE + * TEST_HOST_URL + * TEST_PRIVATE_TOKEN + * + * If any of the above are NULL, all tests in this class will be skipped. If running from mvn simply + * use a command line similar to: + * + * mvn test -DTEST_PRIVATE_TOKEN=your_private_token -DTEST_HOST_URL=https://gitlab.com \ + * -DTEST_NAMESPACE=your_namespace + * + * NOTE: &FixMethodOrder(MethodSorters.NAME_ASCENDING) is very important to insure that the tests are in the correct order + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class TestPager { + + // The following needs to be set to your test repository + + private static final String TEST_NAMESPACE; + private static final String TEST_HOST_URL; + private static final String TEST_PRIVATE_TOKEN; + static { + TEST_NAMESPACE = System.getProperty("TEST_NAMESPACE"); + TEST_HOST_URL = System.getProperty("TEST_HOST_URL"); + TEST_PRIVATE_TOKEN = System.getProperty("TEST_PRIVATE_TOKEN"); + } + + private static GitLabApi gitLabApi; + + public TestPager() { + super(); + } + + @BeforeClass + public static void setup() { + + String problems = ""; + if (TEST_NAMESPACE == null || TEST_NAMESPACE.trim().length() == 0) { + problems += "TEST_NAMESPACE cannot be empty\n"; + } + + if (TEST_HOST_URL == null || TEST_HOST_URL.trim().length() == 0) { + problems += "TEST_HOST_URL cannot be empty\n"; + } + + if (TEST_PRIVATE_TOKEN == null || TEST_PRIVATE_TOKEN.trim().length() == 0) { + problems += "TEST_PRIVATE_TOKEN cannot be empty\n"; + } + + if (problems.isEmpty()) { + gitLabApi = new GitLabApi(ApiVersion.V4, TEST_HOST_URL, TEST_PRIVATE_TOKEN); + } else { + System.err.print(problems); + } + } + + @Before + public void beforeMethod() { + assumeTrue(gitLabApi != null); + } + + @Test + public void testProjectPager() throws GitLabApiException { + + Pager pager = gitLabApi.getProjectApi().getProjects(10); + assertNotNull(pager); + assertEquals(pager.getItemsPerPage(), 10); + assertTrue(0 < pager.getTotalPages()); + assertTrue(0 < pager.getTotalItems()); + + int itemNumber = 0; + int pageIndex = 0; + while (pager.hasNext() && pageIndex < 4) { + + List projects = pager.next(); + + pageIndex++; + assertEquals(pageIndex, pager.getCurrentPage()); + + if (pageIndex < pager.getTotalPages()) + assertEquals(10, projects.size()); + + for (Project project : projects) { + itemNumber++; + System.out.format("page=%d, item=%d, projectId=%d, projectName=%s%n", pageIndex, itemNumber, project.getId(), project.getName()); + } + } + } + + @Test + public void testMemberProjectPager() throws GitLabApiException { + + Pager pager = gitLabApi.getProjectApi().getMemberProjects(2); + assertNotNull(pager); + assertEquals(pager.getItemsPerPage(), 2); + assertTrue(0 < pager.getTotalPages()); + assertTrue(0 < pager.getTotalItems()); + + int itemNumber = 0; + int pageIndex = 0; + while (pager.hasNext() && pageIndex < 10) { + + List projects = pager.next(); + + pageIndex++; + assertEquals(pageIndex, pager.getCurrentPage()); + + if (pageIndex < pager.getTotalPages()) + assertEquals(2, projects.size()); + + for (Project project : projects) { + itemNumber++; + System.out.format("page=%d, item=%d, projectId=%d, projectName=%s%n", pageIndex, itemNumber, project.getId(), project.getName()); + } + } + } +} -- GitLab