diff --git a/src/main/java/org/jenkinsci/plugin/gitea/client/http/PageLinkHeader.java b/src/main/java/org/jenkinsci/plugin/gitea/client/http/PageLinkHeader.java
new file mode 100644
index 0000000..b133e0d
--- /dev/null
+++ b/src/main/java/org/jenkinsci/plugin/gitea/client/http/PageLinkHeader.java
@@ -0,0 +1,74 @@
+package org.jenkinsci.plugin.gitea.client.http;
+
+import java.net.HttpURLConnection;
+
+/**
+ * @see RFC8288
+ */
+public class PageLinkHeader {
+
+ public static final String HEADER = "Link";
+ private static final String PARAMETER_SEPARATOR = ",";
+ private static final String ATTRIBUTE_SEPARATOR = ";";
+
+ private String first;
+ private String last;
+ private String next;
+ private String prev;
+
+ private PageLinkHeader(HttpURLConnection connection) {
+ String headerField = connection.getHeaderField(HEADER);
+ if (headerField != null) {
+ for (String rawLink : headerField.split(PARAMETER_SEPARATOR)) {
+ String[] attributes = rawLink.split(ATTRIBUTE_SEPARATOR);
+ if (attributes.length < 2) {
+ continue;
+ }
+
+ String link = attributes[0].trim();
+ if (!link.startsWith("<") || !link.endsWith(">")) {
+ continue;
+ }
+
+ String parsedLink = link.substring(1, link.length() - 1);
+ for (int i = 1; i < attributes.length; i++) {
+ String[] parameterAttributes = attributes[i].split("=");
+ if (parameterAttributes.length < 2 || !parameterAttributes[0].trim().equals("rel")) {
+ continue;
+ }
+
+ String parameterAttributeValue = parameterAttributes[1].replace("\"", "").toLowerCase();
+ if ("first".equals(parameterAttributeValue)) {
+ first = parsedLink;
+ } else if ("last".equals(parameterAttributeValue)) {
+ last = parsedLink;
+ } else if ("next".equals(parameterAttributeValue)) {
+ next = parsedLink;
+ } else if ("prev".equals(parameterAttributeValue)) {
+ prev = parsedLink;
+ }
+ }
+ }
+ }
+ }
+
+ public static PageLinkHeader from(HttpURLConnection connection) {
+ return new PageLinkHeader(connection);
+ }
+
+ public String getFirst() {
+ return first;
+ }
+
+ public String getLast() {
+ return last;
+ }
+
+ public String getNext() {
+ return next;
+ }
+
+ public String getPrev() {
+ return prev;
+ }
+}
diff --git a/src/main/java/org/jenkinsci/plugin/gitea/client/impl/DefaultGiteaConnection.java b/src/main/java/org/jenkinsci/plugin/gitea/client/impl/DefaultGiteaConnection.java
index 07aeeac..d5f1518 100644
--- a/src/main/java/org/jenkinsci/plugin/gitea/client/impl/DefaultGiteaConnection.java
+++ b/src/main/java/org/jenkinsci/plugin/gitea/client/impl/DefaultGiteaConnection.java
@@ -41,10 +41,11 @@
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
-import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import javax.net.ssl.HttpsURLConnection;
import jenkins.model.Jenkins;
@@ -70,6 +71,7 @@
import org.jenkinsci.plugin.gitea.client.api.GiteaTag;
import org.jenkinsci.plugin.gitea.client.api.GiteaUser;
import org.jenkinsci.plugin.gitea.client.api.GiteaVersion;
+import org.jenkinsci.plugin.gitea.client.http.PageLinkHeader;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -237,7 +239,7 @@ public List fetchRepositories(String username) throws IOExcepti
@Override
public List fetchRepositories(GiteaOwner owner) throws IOException, InterruptedException {
- if(owner instanceof GiteaOrganization) {
+ if (owner instanceof GiteaOrganization) {
return fetchOrganizationRepositories(owner);
}
return fetchRepositories(owner.getUsername());
@@ -245,7 +247,8 @@ public List fetchRepositories(GiteaOwner owner) throws IOExcept
}
@Override
- public List fetchOrganizationRepositories(GiteaOwner owner) throws IOException, InterruptedException {
+ public List fetchOrganizationRepositories(GiteaOwner owner)
+ throws IOException, InterruptedException {
return getList(
api()
.literal("/orgs")
@@ -327,7 +330,8 @@ public GiteaAnnotatedTag fetchAnnotatedTag(String username, String repository, S
}
@Override
- public GiteaAnnotatedTag fetchAnnotatedTag(GiteaRepository repository, GiteaTag tag) throws IOException, InterruptedException {
+ public GiteaAnnotatedTag fetchAnnotatedTag(GiteaRepository repository, GiteaTag tag)
+ throws IOException, InterruptedException {
return fetchAnnotatedTag(repository.getOwner().getUsername(), repository.getName(), tag.getId());
}
@@ -961,34 +965,45 @@ private T patch(UriTemplate template, Object body, final Class modelClass
private List getList(UriTemplate template, final Class modelClass)
throws IOException, InterruptedException {
- HttpURLConnection connection = openConnection(template);
- withAuthentication(connection);
- try {
- connection.connect();
- int status = connection.getResponseCode();
- if (status / 100 == 2) {
- try (InputStream is = connection.getInputStream()) {
- List list = mapper.readerFor(mapper.getTypeFactory()
- .constructCollectionType(List.class, modelClass))
- .readValue(is);
- // strip null values from the list
- for (Iterator iterator = list.iterator(); iterator.hasNext(); ) {
- if (iterator.next() == null) {
- iterator.remove();
- }
+
+ String uri = template.expand();
+
+ List result = new ArrayList<>();
+ while (uri != null) {
+ HttpURLConnection connection = openConnection(uri);
+ withAuthentication(connection);
+ try {
+ connection.connect();
+ int status = connection.getResponseCode();
+ if (status / 100 == 2) {
+ uri = PageLinkHeader.from(connection).getNext();
+ try (InputStream is = connection.getInputStream()) {
+ result.addAll(mapper.readerFor(mapper.getTypeFactory()
+ .constructCollectionType(List.class, modelClass))
+ .readValue(is));
+
+ // strip null values from the list
+ result.removeIf(Objects::isNull);
}
- return list;
+ } else {
+ throw new GiteaHttpStatusException(status, connection.getResponseMessage());
}
+ } finally {
+ connection.disconnect();
}
- throw new GiteaHttpStatusException(status, connection.getResponseMessage());
- } finally {
- connection.disconnect();
}
+
+ return result;
}
@Restricted(NoExternalUse.class)
protected HttpURLConnection openConnection(UriTemplate template) throws IOException {
- URL url = new URL(template.expand());
+ return openConnection(template.expand());
+ }
+
+ @Restricted(NoExternalUse.class)
+ protected HttpURLConnection openConnection(String uri) throws IOException {
+ URL url = new URL(uri);
Jenkins jenkins = Jenkins.get();
if (jenkins.proxy == null) {
return (HttpURLConnection) url.openConnection();
diff --git a/src/test/java/org/jenkinsci/plugin/gitea/client/http/PageLinkHeaderTest.java b/src/test/java/org/jenkinsci/plugin/gitea/client/http/PageLinkHeaderTest.java
new file mode 100644
index 0000000..6bf28aa
--- /dev/null
+++ b/src/test/java/org/jenkinsci/plugin/gitea/client/http/PageLinkHeaderTest.java
@@ -0,0 +1,56 @@
+package org.jenkinsci.plugin.gitea.client.http;
+
+import java.net.HttpURLConnection;
+import org.junit.Assert;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+public class PageLinkHeaderTest {
+
+ @Test
+ public void given__pageable_connection__when__fetch__then__parse() {
+ StringBuilder headerBuilder = new StringBuilder();
+ headerBuilder.append("; rel=\"next\",");
+ headerBuilder.append("; rel=\"last\",");
+ headerBuilder.append("; rel=\"first\",");
+ headerBuilder.append("; rel=\"prev\"");
+
+ HttpURLConnection connect = Mockito.mock(HttpURLConnection.class);
+ Mockito.when(connect.getHeaderField(PageLinkHeader.HEADER)).thenReturn(headerBuilder.toString());
+
+
+ PageLinkHeader linkHeader = PageLinkHeader.from(connect);
+
+ Assert.assertNotNull(linkHeader.getFirst());
+ Assert.assertEquals("http://try.gitea.io/api/v1/orgs/test_org/repos?page=1", linkHeader.getFirst());
+
+ Assert.assertNotNull(linkHeader.getLast());
+ Assert.assertEquals("http://try.gitea.io/api/v1/orgs/test_org/repos?page=3", linkHeader.getLast());
+
+ Assert.assertNotNull(linkHeader.getPrev());
+ Assert.assertEquals("http://try.gitea.io/api/v1/orgs/test_org/repos?page=1", linkHeader.getPrev());
+
+ Assert.assertNotNull(linkHeader.getNext());
+ Assert.assertEquals("http://try.gitea.io/api/v1/orgs/test_org/repos?page=3", linkHeader.getNext());
+ }
+
+ @Test
+ public void given__pageable_rel_not_first__when__fetch__then__parse() {
+ StringBuilder headerBuilder = new StringBuilder();
+ headerBuilder.append("; attr=value; rel=\"next\",");
+ headerBuilder.append("; rel=\"last\",");
+
+ HttpURLConnection connect = Mockito.mock(HttpURLConnection.class);
+ Mockito.when(connect.getHeaderField(PageLinkHeader.HEADER)).thenReturn(headerBuilder.toString());
+
+
+ PageLinkHeader linkHeader = PageLinkHeader.from(connect);
+
+ Assert.assertNotNull(linkHeader.getNext());
+ Assert.assertEquals("http://try.gitea.io/api/v1/orgs/test_org/repos?page=2", linkHeader.getNext());
+
+ Assert.assertNotNull(linkHeader.getLast());
+ Assert.assertEquals("http://try.gitea.io/api/v1/orgs/test_org/repos?page=2", linkHeader.getLast());
+ }
+
+}
diff --git a/src/test/java/org/jenkinsci/plugin/gitea/client/impl/GiteaConnection_DisabledPR_Issues.java b/src/test/java/org/jenkinsci/plugin/gitea/client/impl/GiteaConnection_DisabledPR_Issues.java
index f0cfbfc..642d8a5 100644
--- a/src/test/java/org/jenkinsci/plugin/gitea/client/impl/GiteaConnection_DisabledPR_Issues.java
+++ b/src/test/java/org/jenkinsci/plugin/gitea/client/impl/GiteaConnection_DisabledPR_Issues.java
@@ -14,7 +14,7 @@ public class GiteaConnection_DisabledPR_Issues extends DefaultGiteaConnection {
}
@Override
- protected HttpURLConnection openConnection(UriTemplate template) throws IOException {
+ protected HttpURLConnection openConnection(String uri) throws IOException {
throw new GiteaHttpStatusException(404, "TEST Case");
}
}