LinkHeaderUtil.java
package se.jobtechdev.personaldatagateway.api.util;
import org.springframework.data.domain.Page;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Links;
import org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;
import org.springframework.http.HttpHeaders;
import org.springframework.web.util.UriComponentsBuilder;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
public class LinkHeaderUtil {
private LinkHeaderUtil() {}
public static String format(String rel, String value) {
return String.format("<%s>; rel=\"%s\"", value, rel);
}
public static String getCurrentMethodName() {
final var stackWalker = StackWalker.getInstance();
return stackWalker.walk(s -> s.skip(3).findFirst()).get().getMethodName();
}
public static void handle() throws ClassNotFoundException {
/* NOP */
}
public static Class<?> getCurrentClass() {
try {
handle();
final var className =
StackWalker.getInstance().walk(s -> s.skip(3).findFirst()).get().getClassName();
return Class.forName(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public static Link getSelfLink(Class<?>[] paramTypes, Object... params) {
try {
final var controller = getCurrentClass();
final var proxy = WebMvcLinkBuilder.methodOn(controller);
final var proxyClass = proxy.getClass();
final var method = getCurrentMethodName();
final Method proxyMethod = proxyClass.getMethod(method, paramTypes);
final var proxyMethodResult = proxyMethod.invoke(proxy, params);
final var builder = WebMvcLinkBuilder.linkTo(proxyMethodResult);
return builder.withSelfRel();
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static String getLocationLink(Link link, String id) {
return String.format("%s/%s", link.getHref(), id);
}
public static HttpHeaders createHeadersWithLocation(
String createdResourceId, Class<?>[] paramTypes, Object... params) {
final var headers = new HttpHeaders();
final var link = LinkHeaderUtil.getSelfLink(paramTypes, params);
headers.add(HttpHeaders.LINK, link.toString());
headers.add(HttpHeaders.LOCATION, LinkHeaderUtil.getLocationLink(link, createdResourceId));
headers.add(
HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS,
String.join(",", HttpHeaders.LINK, HttpHeaders.LOCATION));
return headers;
}
public static HttpHeaders createHeaders(Class<?>[] paramTypes, Object... params) {
final var headers = new HttpHeaders();
headers.add(HttpHeaders.LINK, LinkHeaderUtil.getSelfLink(paramTypes, params).toString());
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.LINK);
return headers;
}
public static HttpHeaders createHeaders(
List<Link> links, Class<?>[] paramTypes, Object... params) {
final var headers = new HttpHeaders();
headers.add(
HttpHeaders.LINK,
Links.of(LinkHeaderUtil.getSelfLink(paramTypes, params)).and(links).toString());
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.LINK);
return headers;
}
public static <T> HttpHeaders createHeadersWithPagination(
Page<T> page, Class<?>[] paramTypes, Object... params) {
final var headers = new HttpHeaders();
headers.add("X-Pagination-Total", Long.toString(page.getTotalElements()));
headers.add("X-Pagination-Offset", Integer.toString(page.getNumber() * page.getSize()));
headers.add("X-Pagination-Limit", Integer.toString(page.getSize()));
headers.add("X-Pagination-Count", Long.toString(page.getContent().size()));
final var selfLink = LinkHeaderUtil.getSelfLink(paramTypes, params);
headers.add(
HttpHeaders.LINK,
Links.of(selfLink)
.andIf(
page.getTotalPages() > 0,
Link.of(
UriComponentsBuilder.fromUriString(selfLink.getHref())
.replaceQueryParam("offset", 0)
.toUriString(),
"first"))
.andIf(
page.getTotalPages() > 0 && !page.isFirst(),
Link.of(
UriComponentsBuilder.fromUriString(selfLink.getHref())
.replaceQueryParam(
"offset",
page.previousOrFirstPageable().getPageNumber() * page.getSize())
.toUriString(),
"prev"))
.andIf(
page.getTotalPages() > 0 && !page.isLast(),
Link.of(
UriComponentsBuilder.fromUriString(selfLink.getHref())
.replaceQueryParam(
"offset", page.nextOrLastPageable().getPageNumber() * page.getSize())
.toUriString(),
"next"))
.andIf(
page.getTotalPages() > 0,
Link.of(
UriComponentsBuilder.fromUriString(selfLink.getHref())
.replaceQueryParam(
"offset",
getLastOffset(
page.getTotalPages(), page.getTotalElements(), page.getSize()))
.toUriString(),
"last"))
.toString());
headers.add(
HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS,
String.join(
",",
List.of(
"X-Pagination-Total",
"X-Pagination-Offset",
"X-Pagination-Limit",
"X-Pagination-Count",
HttpHeaders.LINK)));
return headers;
}
static long getLastOffset(int totalPages, long totalElements, int pageSize) {
return totalPages > 0 ? (totalElements % totalPages * pageSize) : 0;
}
}