Skip to content

Instantly share code, notes, and snippets.

@matchilling
Last active December 13, 2023 13:50
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save matchilling/07ba65800a3b0770b7a52d0d868d0f0b to your computer and use it in GitHub Desktop.
Save matchilling/07ba65800a3b0770b7a52d0d868d0f0b to your computer and use it in GitHub Desktop.
REST Pagination in Spring with Link header

RESTful pagination in Spring using Link header

Pagination is a mechanism for managing big result sets in any kind of application. This quick tutorial focuses on implementing pagination in a RESTful API, using Spring MVC emoji-leaves and Spring Data without the help of the Spring HATEOAS project.

Continue reading ...

package com.matchilling.api.rest.data
import com.fasterxml.jackson.core.JsonGenerator
import com.fasterxml.jackson.databind.JsonSerializer
import com.fasterxml.jackson.databind.SerializerProvider
import org.springframework.boot.jackson.JsonComponent
import org.springframework.data.domain.PageImpl
import java.io.IOException
@JsonComponent
class PageSerializer : JsonSerializer<PageImpl<*>>() {
@Throws(IOException::class)
override fun serialize(
page: PageImpl<*>,
jsonGenerator: JsonGenerator,
serializerProvider: SerializerProvider
) {
jsonGenerator.writeObject(page.content)
}
}
package com.matchilling.api.rest.servlet
import org.springframework.beans.factory.annotation.Value
import org.springframework.core.MethodParameter
import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.Pageable
import org.springframework.http.MediaType
import org.springframework.http.converter.HttpMessageConverter
import org.springframework.http.server.ServerHttpRequest
import org.springframework.http.server.ServerHttpResponse
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice
import org.springframework.web.util.UriComponentsBuilder
@RestControllerAdvice
class PaginatedResponseAdvice<T>(
@Value("\${spring.data.web.pageable.one-indexed-parameters}")
private val oneIndexed: Boolean
) : ResponseBodyAdvice<T> {
override fun supports(
returnType: MethodParameter,
converterType: Class<out HttpMessageConverter<*>>
): Boolean {
return PageImpl::class.java.isAssignableFrom(returnType.parameterType)
}
override fun beforeBodyWrite(
page: T?,
returnType: MethodParameter,
selectedContentType: MediaType,
selectedConverterType: Class<out HttpMessageConverter<*>>,
request: ServerHttpRequest,
response: ServerHttpResponse
): T? {
if (page !is PageImpl<*>) {
return page
}
val headers = response.headers
headers.set(
"Access-Control-Expose-Headers",
"Link,Page-Number,Page-Size,Total-Elements,Total-Pages"
)
val links = page.links(request)
if (links.isNotBlank()) {
headers.set("Link", links)
}
val pageNumber = if (oneIndexed)
page.number.plus(1)
else
page.number
headers.set("Page-Number", pageNumber.toString())
headers.set("Page-Size", page.size.toString())
headers.set("Total-Elements", page.totalElements.toString())
headers.set("Total-Pages", page.totalPages.toString())
return page
}
private fun PageImpl<*>.links(request: ServerHttpRequest): String {
val links = mutableListOf<String>()
val builder = UriComponentsBuilder.fromUri(request.uri)
if (request.uri.host == "localhost") {
builder.port(request.uri.port)
}
if (!this.isFirst) {
val link = builder.replacePageAndSize(this.pageable.first())
links.add("<${link.toUriString()}>; rel=\"first\"")
}
if (this.hasPrevious()) {
val link = builder.replacePageAndSize(this.previousPageable())
links.add("<${link.toUriString()}>; rel=\"prev\"")
}
if (this.hasNext()) {
val link = builder.replacePageAndSize(this.nextPageable())
links.add("<${link.toUriString()}>; rel=\"next\"")
}
if (!this.isLast) {
val last = builder.cloneBuilder()
last.replaceQueryParam("page", this.totalPages)
last.replaceQueryParam("size", this.size)
links.add("<${last.toUriString()}>; rel=\"last\"")
}
return links.joinToString(",")
}
private fun UriComponentsBuilder.replacePageAndSize(
page: Pageable
): UriComponentsBuilder {
val builder = this.cloneBuilder()
val pageNumber = if (oneIndexed)
page.pageNumber.plus(1)
else
page.pageNumber
builder.replaceQueryParam("page", pageNumber)
builder.replaceQueryParam("size", page.pageSize)
return builder
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment