Skip to content

Commit

Permalink
#17 - implements pagination support
Browse files Browse the repository at this point in the history
  • Loading branch information
tjuerge committed Nov 18, 2023
1 parent f3bd5ef commit 66352db
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 200 deletions.
167 changes: 0 additions & 167 deletions src/main/java/org/vaulttec/idm/sync/app/LinkHeader.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestClientException;
import org.vaulttec.idm.sync.app.AbstractRestClient;
import org.vaulttec.idm.sync.app.LinkHeader;
import org.vaulttec.util.LinkHeader;
import org.vaulttec.idm.sync.app.gitlab.model.GLGroup;
import org.vaulttec.idm.sync.app.gitlab.model.GLPermission;
import org.vaulttec.idm.sync.app.gitlab.model.GLProject;
Expand Down Expand Up @@ -71,16 +71,16 @@ protected <T> List<T> makeReadListApiCall(String apiCall, ParameterizedTypeRefer
List<T> entities;
ResponseEntity<List<T>> response = restTemplate.exchange(url, HttpMethod.GET, authenticationEntity, typeReference,
uriVariables);
LinkHeader linkHeader = LinkHeader.parse(response.getHeaders());
LinkHeader linkHeader = LinkHeader.parse(response.getHeaders(), "page", "per_page");
if (linkHeader == null || !linkHeader.hasLink(LinkHeader.Rel.NEXT)) {
entities = response.getBody();
} else {
entities = new ArrayList<>(response.getBody());
do {
URI nextResourceUri = linkHeader.getLink(LinkHeader.Rel.NEXT).getResourceUri();
URI nextResourceUri = linkHeader.getLink(LinkHeader.Rel.NEXT).resourceUri();
response = restTemplate.exchange(nextResourceUri, HttpMethod.GET, authenticationEntity, typeReference);
entities.addAll(response.getBody());
linkHeader = LinkHeader.parse(response.getHeaders());
linkHeader = LinkHeader.parse(response.getHeaders(), "page", "per_page");
} while (linkHeader != null && linkHeader.hasLink(LinkHeader.Rel.NEXT));
}
return entities;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.vaulttec.util.LinkHeader;

public class KeycloakClient extends AbstractRestClient {

Expand Down Expand Up @@ -81,7 +82,8 @@ protected <T> List<T> makeReadListApiCall(String apiCall, ParameterizedTypeRefer
List<T> entities;
ResponseEntity<List<T>> response = restTemplate.exchange(url, HttpMethod.GET, authenticationEntity, typeReference,
uriVariables);
if (response.getBody().size() < perPage) {
LinkHeader linkHeader = LinkHeader.parse(response.getHeaders(), "first", "max");
if (linkHeader == null || !linkHeader.hasLink(LinkHeader.Rel.NEXT)) {
entities = response.getBody();
} else {
entities = new ArrayList<>(response.getBody());
Expand All @@ -90,7 +92,8 @@ protected <T> List<T> makeReadListApiCall(String apiCall, ParameterizedTypeRefer
uriVariables.put("first", Integer.toString(first));
response = restTemplate.exchange(url, HttpMethod.GET, authenticationEntity, typeReference, uriVariables);
entities.addAll(response.getBody());
} while (response.getBody().size() == perPage);
linkHeader = LinkHeader.parse(response.getHeaders(), "first", "max");
} while (linkHeader != null && linkHeader.hasLink(LinkHeader.Rel.NEXT));
return entities;
}
return entities;
Expand Down Expand Up @@ -136,7 +139,7 @@ public List<IdpUser> getUsers(String search) {
apiCall += "?search={search}";
uriVariables.put("search", search);
}
return makeReadApiCall(apiCall, RESPONSE_TYPE_USERS, uriVariables);
return makeReadListApiCall(apiCall, RESPONSE_TYPE_USERS, uriVariables);
}

public boolean updateUserAttributes(IdpUser user, Map<String, List<String>> attributes) {
Expand Down Expand Up @@ -188,7 +191,7 @@ public List<IdpGroup> getGroups(String search) {
apiCall += "?search={search}";
uriVariables.put("search", search);
}
return makeReadApiCall(apiCall, RESPONSE_TYPE_GROUPS, uriVariables);
return makeReadListApiCall(apiCall, RESPONSE_TYPE_GROUPS, uriVariables);
}

public boolean updateGroupAttributes(IdpGroup group, Map<String, List<String>> attributes) {
Expand Down Expand Up @@ -220,15 +223,7 @@ public List<IdpUser> getGroupMembers(IdpGroup group) {
LOG.debug("Retrieving group members from group '{}", group.getPath());
String apiCall = "/admin/realms/{realm}/groups/{groupId}/members";
Map<String, String> uriVariables = createUriVariables("realm", realm, "groupId", group.getId());
return makeReadApiCall(apiCall, RESPONSE_TYPE_USERS, uriVariables);
}

@Override
protected void prepareAuthenticationEntity(String headerName, String headerValue) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.set(headerName, headerValue);
authenticationEntity = new HttpEntity<String>(headers);
return makeReadListApiCall(apiCall, RESPONSE_TYPE_USERS, uriVariables);
}

private HttpEntity<String> createLoginEntity(String clientId, String clientSecret) {
Expand Down
132 changes: 132 additions & 0 deletions src/main/java/org/vaulttec/util/LinkHeader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/*
* IDM Syncronizer
* Copyright (c) 2018 Torsten Juergeleit
* mailto:torsten AT vaulttec DOT org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.vaulttec.util;

import org.springframework.http.HttpHeaders;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class LinkHeader {
private final Map<Rel, Link> links;

protected LinkHeader(Map<Rel, Link> links) {
this.links = links;
}

public boolean hasLink(Rel rel) {
return links.containsKey(rel);
}

public Link getLink(Rel rel) {
return links.get(rel);
}

public static LinkHeader parse(HttpHeaders headers, String pageParameterName, String perPageParameterName ) {
String linkValue = headers.getFirst(HttpHeaders.LINK);
if (StringUtils.hasText(linkValue)) {
String[] linkValues = linkValue.split(",");
if (linkValues.length > 0) {
Map<Rel, Link> links = new HashMap<>();
for (String value : linkValues) {
Link link = Link.fromSource(value, pageParameterName, perPageParameterName);
if (link != null && link.rel() != null) {
links.put(link.rel(), link);
}
}
return new LinkHeader(links);
}

}
return null;
}

public enum Rel {
FIRST, PREV, NEXT, LAST;

public static Rel fromSource(String source) {
if (source != null) {
for (Rel rel : Rel.values()) {
String pattern = "rel=\"" + rel.name().toLowerCase() + "\"";
if (source.contains(pattern)) {
return rel;
}
}
}
return null;
}
}

public record Link(URI resourceUri, int page, int perPage, Rel rel) {
private static final Pattern QUERY_PATTERN = Pattern.compile("([^&=]+)=?([^&]+)?");

public static Link fromSource(String source, String pageParameterName, String perPageParameterName) {
if (ObjectUtils.isEmpty(source)) {
return null;
}
URI resourceUri = getResourceUri(source);
Rel rel = Rel.fromSource(source);
int page = getQueryParameterValue(resourceUri, pageParameterName);
int perPage = getQueryParameterValue(resourceUri, perPageParameterName);
return new Link(resourceUri, page, perPage, rel);
}

private static URI getResourceUri(String source) {
int startIndex = source.indexOf("<");
int endIndex = source.indexOf(">");
if (startIndex < 0 || endIndex < 0) {
return null;
} else {
String resourceString = source.substring(startIndex, endIndex + 1);
try {
return new URI(resourceString.substring(1, resourceString.length() - 1));
} catch (URISyntaxException e) {
throw new IllegalStateException("", e);
}
}
}

private static int getQueryParameterValue(URI resourceUri, String paramName) {
if (resourceUri == null) {
return -1;
}
String query = resourceUri.getQuery();
if (query == null)
return -1;
Matcher matcher = QUERY_PATTERN.matcher(query);
while (matcher.find()) {
String name = matcher.group(1);
String value = matcher.group(2);
if (name.equals(paramName)) {
if (ObjectUtils.isEmpty(value)) {
return -1;
} else {
return Integer.parseInt(value);
}
}
}
return -1;
}
}
}
Loading

0 comments on commit 66352db

Please sign in to comment.