diff --git a/src/main/java/org/springframework/data/web/PagedModel.java b/src/main/java/org/springframework/data/web/PagedModel.java index a7a891bf7c..48ba1c5fde 100644 --- a/src/main/java/org/springframework/data/web/PagedModel.java +++ b/src/main/java/org/springframework/data/web/PagedModel.java @@ -33,22 +33,27 @@ * * @author Oliver Drotbohm * @author Greg Turnquist + * @author Lazar Radinović * @since 3.3 */ public class PagedModel { private final Page page; + private final boolean oneIndexedParameters; + /** * Creates a new {@link PagedModel} for the given {@link Page}. * - * @param page must not be {@literal null}. + * @param page must not be {@literal null}. + * @param oneIndexedParameters indicates weather to serialize page number by adding 0 or 1 */ - public PagedModel(Page page) { + public PagedModel(Page page, boolean oneIndexedParameters) { Assert.notNull(page, "Page must not be null"); this.page = page; + this.oneIndexedParameters = oneIndexedParameters; } @JsonProperty @@ -59,7 +64,7 @@ public List getContent() { @Nullable @JsonProperty("page") public PageMetadata getMetadata() { - return new PageMetadata(page.getSize(), page.getNumber(), page.getTotalElements(), + return new PageMetadata(page.getSize(), page.getNumber() + (oneIndexedParameters ? 1 : 0), page.getTotalElements(), page.getTotalPages()); } diff --git a/src/main/java/org/springframework/data/web/config/PageModuleCustomizer.java b/src/main/java/org/springframework/data/web/config/PageModuleCustomizer.java new file mode 100644 index 0000000000..3f3bda6ab6 --- /dev/null +++ b/src/main/java/org/springframework/data/web/config/PageModuleCustomizer.java @@ -0,0 +1,19 @@ +package org.springframework.data.web.config; + +import org.springframework.data.web.SortHandlerMethodArgumentResolver; + +/** + * Callback interface that can be implemented by beans wishing to customize the + * {@link SpringDataJacksonConfiguration.PageModule} configuration. + * + * @author Lazar Radinović + * @since 3.4.0 + */ +public interface PageModuleCustomizer { + /** + * Customize the given {@link SpringDataJacksonConfiguration.PageModule}. + * + * @param pageModule the {@link SpringDataJacksonConfiguration.PageModule} to customize, will never be {@literal null}. + */ + void customize(SpringDataJacksonConfiguration.PageModule pageModule); +} diff --git a/src/main/java/org/springframework/data/web/config/SpringDataJacksonConfiguration.java b/src/main/java/org/springframework/data/web/config/SpringDataJacksonConfiguration.java index 460abe39bd..0bc50d59b2 100644 --- a/src/main/java/org/springframework/data/web/config/SpringDataJacksonConfiguration.java +++ b/src/main/java/org/springframework/data/web/config/SpringDataJacksonConfiguration.java @@ -15,6 +15,7 @@ */ package org.springframework.data.web.config; +import java.io.IOException; import java.util.List; import org.slf4j.Logger; @@ -31,12 +32,14 @@ import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.SerializationConfig; -import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.BeanPropertyWriter; import com.fasterxml.jackson.databind.ser.BeanSerializerModifier; import com.fasterxml.jackson.databind.ser.std.ToStringSerializerBase; import com.fasterxml.jackson.databind.util.StdConverter; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; /** * JavaConfig class to export Jackson specific configuration. @@ -53,8 +56,12 @@ public GeoModule jacksonGeoModule() { } @Bean - public PageModule pageModule() { - return new PageModule(settings); + public PageModule pageModule(@Autowired(required = false) PageModuleCustomizer customizer) { + PageModule module = new PageModule(settings); + if(customizer != null) { + customizer.customize(module); + } + return module; } /** @@ -79,6 +86,9 @@ public static class PageModule extends SimpleModule { UNPAGED_TYPE = ClassUtils.resolveClassName(UNPAGED_TYPE_NAME, PageModule.class.getClassLoader()); } + private boolean oneIndexedParameters; + private PageModelConverter pageModelConverter; + /** * Creates a new {@link PageModule} for the given {@link SpringDataWebSettings}. * @@ -92,10 +102,39 @@ public PageModule(@Nullable SpringDataWebSettings settings) { setSerializerModifier(new WarningLoggingModifier()); } else { - setMixInAnnotation(PageImpl.class, WrappingMixing.class); + pageModelConverter = new PageModelConverter(); + addSerializer(PageImpl.class, new JsonSerializer<>() { + @Override + public void serialize(PageImpl page, JsonGenerator gen, SerializerProvider providers) throws IOException { + gen.writeObject(pageModelConverter.convert(page)); + } + }); } } + /** + * Configures whether to expose and assume 1-based page number indexes in the request parameters. Defaults to + * {@literal false}, meaning a page number of 0 in the request equals the first page. If this is set to + * {@literal true}, a page number of 1 in the request will be considered the first page. + * + * @param oneIndexedParameters the oneIndexedParameters to set + */ + public void setOneIndexedParameters(boolean oneIndexedParameters) { + this.oneIndexedParameters = oneIndexedParameters; + this.pageModelConverter.setOneIndexedParameters(oneIndexedParameters); + } + + /** + * Indicates whether to expose and assume 1-based page number indexes in the request parameters. Defaults to + * {@literal false}, meaning a page number of 0 in the request equals the first page. If this is set to + * {@literal true}, a page number of 1 in the request will be considered the first page. + * + * @return whether to assume 1-based page number indexes in the request parameters. + */ + public boolean isOneIndexedParameters() { + return oneIndexedParameters; + } + /** * A Jackson serializer rendering instances of {@link org.springframework.data.domain.Unpaged} as {@code INSTANCE} * as it was previous rendered. @@ -116,15 +155,18 @@ public String valueToString(@Nullable Object value) { } } - @JsonSerialize(converter = PageModelConverter.class) - abstract class WrappingMixing {} - static class PageModelConverter extends StdConverter, PagedModel> { + public boolean oneIndexedParameters; + + public void setOneIndexedParameters(boolean oneIndexedParameters) { + this.oneIndexedParameters = oneIndexedParameters; + } + @Nullable @Override public PagedModel convert(@Nullable Page value) { - return value == null ? null : new PagedModel<>(value); + return value == null ? null : new PagedModel<>(value, oneIndexedParameters); } }