DATAREST-909 - Revert Jackson-aware Sort and Pageable translation.
Revert changes introduced by DATAREST-883 - Jackson-aware field translation in Sort and DATAREST-906 - Consider default pageable if Sort is null. The way how Sort translation was implemented breaks Sort and Pageable argument resolution for custom controllers as a domain type is always required. Argument resolution fails if the related domain type cannot be resolved. Related pull requests: #231, #222.
This commit is contained in:
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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.springframework.data.rest.webmvc.jpa;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.tests.AbstractWebIntegrationTests;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestController;
|
||||
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
|
||||
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
/**
|
||||
* Web integration tests specific to default {@link Pageable} handling.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@WebAppConfiguration
|
||||
@ContextConfiguration(classes = JpaDefaultPageableWebTests.Config.class)
|
||||
public class JpaDefaultPageableWebTests extends AbstractWebIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ RepositoryRestMvcConfiguration.class, JpaRepositoryConfig.class })
|
||||
@EnableJpaRepositories(considerNestedRepositories = true)
|
||||
static class Config extends RepositoryRestConfigurerAdapter {
|
||||
|
||||
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
|
||||
config.setDefaultPageSize(1);
|
||||
}
|
||||
}
|
||||
|
||||
@RepositoryRestController
|
||||
public static class MyRestController {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, path = "books/default-pageable")
|
||||
@ResponseBody
|
||||
Page<Book> getDefaultPageable(Pageable pageable) {
|
||||
|
||||
if (pageable != null) {
|
||||
return new PageImpl<Book>(Collections.singletonList(new Book()), pageable, 1);
|
||||
}
|
||||
|
||||
return new PageImpl<Book>(Collections.emptyList(), pageable, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired TestDataPopulator loader;
|
||||
@Autowired ApplicationContext context;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
loader.populateRepositories();
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-906
|
||||
*/
|
||||
@Test
|
||||
public void executesSearchThatTakesAMappedSortProperty() throws Exception {
|
||||
|
||||
Link findBySortedLink = client.discoverUnique("books", "search", "find-spring-books-sorted");
|
||||
|
||||
// Assert sort options advertised
|
||||
assertThat(findBySortedLink.isTemplated(), is(true));
|
||||
assertThat(findBySortedLink.getVariableNames(), hasItems("sort", "projection"));
|
||||
|
||||
// Assert results returned as specified
|
||||
client.follow(findBySortedLink.expand()).//
|
||||
andExpect(jsonPath("$._embedded.books[0].title").exists()).//
|
||||
andExpect(jsonPath("$._embedded.books[1].title").doesNotExist());
|
||||
|
||||
client.follow(findBySortedLink.expand("sales,desc")).//
|
||||
andExpect(jsonPath("$._embedded.books[0].title").exists()).//
|
||||
andExpect(jsonPath("$._embedded.books[1].title").doesNotExist());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-906
|
||||
*/
|
||||
@Test
|
||||
public void shouldApplyDefaultPageable() throws Exception {
|
||||
|
||||
mvc.perform(get("/books/default-pageable")).andDo(print()) //
|
||||
.andExpect(jsonPath("$.content[0].sales").value(0)) //
|
||||
.andExpect(jsonPath("$.size").value(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-906
|
||||
*/
|
||||
@Test
|
||||
public void shouldOverrideDefaultPageable() throws Exception {
|
||||
|
||||
mvc.perform(get("/books/default-pageable?size=10")).andDo(print()) //
|
||||
.andExpect(jsonPath("$.content[0].sales").value(0)) //
|
||||
.andExpect(jsonPath("$.size").value(10));
|
||||
}
|
||||
}
|
||||
@@ -86,11 +86,7 @@ import org.springframework.data.rest.webmvc.convert.UriListHttpMessageConverter;
|
||||
import org.springframework.data.rest.webmvc.json.DomainObjectReader;
|
||||
import org.springframework.data.rest.webmvc.json.EnumTranslator;
|
||||
import org.springframework.data.rest.webmvc.json.Jackson2DatatypeHelper;
|
||||
import org.springframework.data.rest.webmvc.json.JacksonMappingAwareSortTranslator;
|
||||
import org.springframework.data.rest.webmvc.json.JacksonSerializers;
|
||||
import org.springframework.data.rest.webmvc.json.MappingAwareDefaultedPageableArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.json.MappingAwarePageableArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.json.MappingAwareSortArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module;
|
||||
import org.springframework.data.rest.webmvc.json.PersistentEntityToJsonSchemaConverter;
|
||||
import org.springframework.data.rest.webmvc.spi.BackendIdConverter;
|
||||
@@ -98,7 +94,6 @@ import org.springframework.data.rest.webmvc.spi.BackendIdConverter.DefaultIdConv
|
||||
import org.springframework.data.rest.webmvc.support.BackendIdHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.support.DefaultedPageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping;
|
||||
import org.springframework.data.rest.webmvc.support.DomainClassResolver;
|
||||
import org.springframework.data.rest.webmvc.support.ETagArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.support.HttpMethodHandlerMethodArgumentResolver;
|
||||
import org.springframework.data.rest.webmvc.support.JpaHelper;
|
||||
@@ -151,12 +146,11 @@ import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
* @author Oliver Gierke
|
||||
* @author Jon Brisbin
|
||||
* @author Greg Turnquist
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Configuration
|
||||
@EnableHypermediaSupport(type = HypermediaType.HAL)
|
||||
@ComponentScan(basePackageClasses = RepositoryRestController.class,
|
||||
includeFilters = @Filter(BasePathAwareController.class), useDefaultFilters = false)
|
||||
includeFilters = @Filter(BasePathAwareController.class) , useDefaultFilters = false)
|
||||
@ImportResource("classpath*:META-INF/spring-data-rest/**/*.xml")
|
||||
@Import({ SpringDataJacksonConfiguration.class, EnableSpringDataWebSupport.QuerydslActivator.class })
|
||||
public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebConfiguration implements InitializingBean {
|
||||
@@ -719,17 +713,10 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
resourceMappings());
|
||||
|
||||
HateoasPageableHandlerMethodArgumentResolver pageableResolver = pageableResolver();
|
||||
|
||||
JacksonMappingAwareSortTranslator sortTranslator = new JacksonMappingAwareSortTranslator(objectMapper(),
|
||||
repositories(), DomainClassResolver.of(repositories(), resourceMappings(), baseUri()));
|
||||
|
||||
HandlerMethodArgumentResolver sortResolver = new MappingAwareSortArgumentResolver(sortTranslator, sortResolver());
|
||||
HandlerMethodArgumentResolver jacksonPageableResolver = new MappingAwarePageableArgumentResolver(sortTranslator,
|
||||
HandlerMethodArgumentResolver defaultedPageableResolver = new DefaultedPageableHandlerMethodArgumentResolver(
|
||||
pageableResolver);
|
||||
HandlerMethodArgumentResolver defaultedPageableResolver = new MappingAwareDefaultedPageableArgumentResolver(
|
||||
sortTranslator, pageableResolver);
|
||||
|
||||
return Arrays.asList(defaultedPageableResolver, jacksonPageableResolver, sortResolver,
|
||||
return Arrays.asList(defaultedPageableResolver, pageableResolver, sortResolver(),
|
||||
serverHttpRequestMethodArgumentResolver(), repoRequestArgumentResolver(), persistentEntityArgumentResolver(),
|
||||
resourceMetadataHandlerMethodArgumentResolver(), HttpMethodHandlerMethodArgumentResolver.INSTANCE, peraResolver,
|
||||
backendIdHandlerMethodArgumentResolver(), eTagArgumentResolver());
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.rest.webmvc.json;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@@ -30,8 +31,12 @@ import org.springframework.data.rest.webmvc.mapping.AssociationLinks;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.databind.BeanDescription;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.introspect.BasicClassIntrospector;
|
||||
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
|
||||
import com.fasterxml.jackson.databind.introspect.ClassIntrospector;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
/**
|
||||
@@ -40,13 +45,13 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
* detect nested objects, lookup the original value and apply the merge recursively.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
* @since 2.2
|
||||
*/
|
||||
public class DomainObjectReader {
|
||||
|
||||
private final PersistentEntities entities;
|
||||
private final AssociationLinks associationLinks;
|
||||
private final ClassIntrospector introspector;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DomainObjectReader} using the given {@link PersistentEntities} and {@link ResourceMappings}.
|
||||
@@ -61,12 +66,13 @@ public class DomainObjectReader {
|
||||
|
||||
this.entities = entities;
|
||||
this.associationLinks = new AssociationLinks(mappings);
|
||||
this.introspector = new BasicClassIntrospector();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the given input stream into an {@link ObjectNode} and applies that to the given existing instance.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @param request must not be {@literal null}.
|
||||
* @param target must not be {@literal null}.
|
||||
* @param mapper must not be {@literal null}.
|
||||
* @return
|
||||
@@ -104,7 +110,7 @@ public class DomainObjectReader {
|
||||
|
||||
Assert.notNull(entity, "No PersistentEntity found for ".concat(type.getName()).concat("!"));
|
||||
|
||||
final MappedProperties properties = MappedProperties.fromJacksonProperties(entity, mapper);
|
||||
final MappedProperties properties = getJacksonProperties(entity, mapper);
|
||||
|
||||
entity.doWithProperties(new SimplePropertyHandler() {
|
||||
|
||||
@@ -151,7 +157,6 @@ public class DomainObjectReader {
|
||||
* @return
|
||||
* @throws Exception
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T doMerge(ObjectNode root, T target, ObjectMapper mapper) throws Exception {
|
||||
|
||||
Assert.notNull(root, "Root ObjectNode must not be null!");
|
||||
@@ -164,7 +169,7 @@ public class DomainObjectReader {
|
||||
return mapper.readerForUpdating(target).readValue(root);
|
||||
}
|
||||
|
||||
MappedProperties mappedProperties = MappedProperties.fromJacksonProperties(entity, mapper);
|
||||
MappedProperties mappedProperties = getJacksonProperties(entity, mapper);
|
||||
|
||||
for (Iterator<Entry<String, JsonNode>> i = root.fields(); i.hasNext();) {
|
||||
|
||||
@@ -249,4 +254,63 @@ public class DomainObjectReader {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MappedProperties} for the given {@link PersistentEntity}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param mapper must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private MappedProperties getJacksonProperties(PersistentEntity<?, ?> entity, ObjectMapper mapper) {
|
||||
|
||||
BeanDescription description = introspector.forDeserialization(mapper.getDeserializationConfig(),
|
||||
mapper.constructType(entity.getType()), mapper.getDeserializationConfig());
|
||||
|
||||
return new MappedProperties(entity, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple value object to capture a mapping of Jackson mapped field names and {@link PersistentProperty} instances.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class MappedProperties {
|
||||
|
||||
private final Map<PersistentProperty<?>, String> propertyToFieldName;
|
||||
private final Map<String, PersistentProperty<?>> fieldNameToProperty;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MappedProperties} instance for the given {@link PersistentEntity} and
|
||||
* {@link BeanDescription}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param description must not be {@literal null}.
|
||||
*/
|
||||
public MappedProperties(PersistentEntity<?, ?> entity, BeanDescription description) {
|
||||
|
||||
this.propertyToFieldName = new HashMap<PersistentProperty<?>, String>();
|
||||
this.fieldNameToProperty = new HashMap<String, PersistentProperty<?>>();
|
||||
|
||||
for (BeanPropertyDefinition property : description.findProperties()) {
|
||||
|
||||
PersistentProperty<?> persistentProperty = entity.getPersistentProperty(property.getInternalName());
|
||||
|
||||
propertyToFieldName.put(persistentProperty, property.getName());
|
||||
fieldNameToProperty.put(property.getName(), persistentProperty);
|
||||
}
|
||||
}
|
||||
|
||||
public String getMappedName(PersistentProperty<?> property) {
|
||||
return propertyToFieldName.get(property);
|
||||
}
|
||||
|
||||
public boolean hasPersistentPropertyForField(String fieldName) {
|
||||
return fieldNameToProperty.containsKey(fieldName);
|
||||
}
|
||||
|
||||
public PersistentProperty<?> getPersistentProperty(String fieldName) {
|
||||
return fieldNameToProperty.get(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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.springframework.data.rest.webmvc.json;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Order;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.webmvc.support.DomainClassResolver;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Translator for {@link Sort} arguments that is aware of Jackson-Mapping on domain classes. Jackson field names are
|
||||
* translated to {@link PersistentProperty} names. Domain class are looked up by resolving request URLs to mapped
|
||||
* repositories.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 2.6, 2.5.3, 2.4.5
|
||||
*/
|
||||
public class JacksonMappingAwareSortTranslator {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
private final Repositories repositories;
|
||||
private final DomainClassResolver domainClassResolver;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JacksonMappingAwareSortTranslator} for the given {@link ObjectMapper}, {@link Repositories}
|
||||
* and {@link DomainClassResolver}.
|
||||
*
|
||||
* @param objectMapper must not be {@literal null}.
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param domainClassResolver must not be {@literal null}.
|
||||
*/
|
||||
public JacksonMappingAwareSortTranslator(ObjectMapper objectMapper, Repositories repositories,
|
||||
DomainClassResolver domainClassResolver) {
|
||||
|
||||
Assert.notNull(objectMapper, "ObjectMapper must not be null!");
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(domainClassResolver, "DomainClassResolver must not be null!");
|
||||
|
||||
this.objectMapper = objectMapper;
|
||||
this.repositories = repositories;
|
||||
this.domainClassResolver = domainClassResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates Jackson field names within a {@link Sort} to {@link PersistentProperty} property names.
|
||||
*
|
||||
* @param input must not be {@literal null}.
|
||||
* @param parameter must not be {@literal null}.
|
||||
* @param webRequest must not be {@literal null}.
|
||||
* @return a {@link Sort} containing translated property names or {@literal null} the resulting {@link Sort} contains
|
||||
* no properties.
|
||||
*/
|
||||
protected Sort translateSort(Sort input, MethodParameter parameter, NativeWebRequest webRequest) {
|
||||
|
||||
Assert.notNull(input, "Sort must not be null!");
|
||||
Assert.notNull(parameter, "MethodParameter must not be null!");
|
||||
Assert.notNull(webRequest, "NativeWebRequest must not be null!");
|
||||
|
||||
Class<?> domainClass = domainClassResolver.resolve(parameter.getMethod(), webRequest);
|
||||
PersistentEntity<?, ?> persistentEntity = repositories.getPersistentEntity(domainClass);
|
||||
MappedProperties mappedProperties = MappedProperties.fromJacksonProperties(persistentEntity, objectMapper);
|
||||
|
||||
return new SortTranslator(mappedProperties).translateSort(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates {@link Sort} orders from Jackson-mapped field names to {@link PersistentProperty} names.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Oliver Gierke
|
||||
* @since 2.6, 2.5.3, 2.4.5
|
||||
*/
|
||||
static class SortTranslator {
|
||||
|
||||
private final MappedProperties mappedProperties;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SortTranslator}.
|
||||
*
|
||||
* @param mappedProperties must not be {@literal null}.
|
||||
*/
|
||||
public SortTranslator(MappedProperties mappedProperties) {
|
||||
|
||||
Assert.notNull(mappedProperties, "MappedProperties must not be null!");
|
||||
|
||||
this.mappedProperties = mappedProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates {@link Sort} orders from Jackson-mapped field names to {@link PersistentProperty} names. Properties
|
||||
* that cannot be resolved are dropped.
|
||||
*
|
||||
* @param input must not be {@literal null}.
|
||||
* @return {@link Sort} with translated field names or {@literal null} if translation dropped all sort fields.
|
||||
*/
|
||||
Sort translateSort(Sort input) {
|
||||
|
||||
List<Order> filteredOrders = new ArrayList<Order>();
|
||||
|
||||
for (Order order : input) {
|
||||
|
||||
if (mappedProperties.hasPersistentPropertyForField(order.getProperty())) {
|
||||
|
||||
PersistentProperty<?> persistentProperty = mappedProperties.getPersistentProperty(order.getProperty());
|
||||
Order mappedOrder = new Order(order.getDirection(), persistentProperty.getName(), order.getNullHandling());
|
||||
filteredOrders.add(order.isIgnoreCase() ? mappedOrder.ignoreCase() : mappedOrder);
|
||||
}
|
||||
}
|
||||
|
||||
return filteredOrders.isEmpty() ? null : new Sort(filteredOrders);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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.springframework.data.rest.webmvc.json;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.databind.BeanDescription;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.introspect.BasicClassIntrospector;
|
||||
import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
|
||||
import com.fasterxml.jackson.databind.introspect.ClassIntrospector;
|
||||
|
||||
/**
|
||||
* Simple value object to capture a mapping of Jackson mapped field names and {@link PersistentProperty} instances.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class MappedProperties {
|
||||
|
||||
private static final ClassIntrospector INTROSPECTOR = new BasicClassIntrospector();
|
||||
|
||||
private final Map<PersistentProperty<?>, String> propertyToFieldName;
|
||||
private final Map<String, PersistentProperty<?>> fieldNameToProperty;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MappedProperties} instance for the given {@link PersistentEntity} and {@link BeanDescription}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param description must not be {@literal null}.
|
||||
*/
|
||||
private MappedProperties(PersistentEntity<?, ?> entity, BeanDescription description) {
|
||||
|
||||
this.propertyToFieldName = new HashMap<PersistentProperty<?>, String>();
|
||||
this.fieldNameToProperty = new HashMap<String, PersistentProperty<?>>();
|
||||
|
||||
for (BeanPropertyDefinition property : description.findProperties()) {
|
||||
|
||||
PersistentProperty<?> persistentProperty = entity.getPersistentProperty(property.getInternalName());
|
||||
|
||||
propertyToFieldName.put(persistentProperty, property.getName());
|
||||
fieldNameToProperty.put(property.getName(), persistentProperty);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates {@link MappedProperties} for the given {@link PersistentEntity}.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param mapper must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public static MappedProperties fromJacksonProperties(PersistentEntity<?, ?> entity, ObjectMapper mapper) {
|
||||
|
||||
BeanDescription description = INTROSPECTOR.forDeserialization(mapper.getDeserializationConfig(),
|
||||
mapper.constructType(entity.getType()), mapper.getDeserializationConfig());
|
||||
|
||||
return new MappedProperties(entity, description);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param property must not be {@literal null}
|
||||
* @return the mapped name for the {@link PersistentProperty}
|
||||
*/
|
||||
public String getMappedName(PersistentProperty<?> property) {
|
||||
|
||||
Assert.notNull(property, "PersistentProperty must not be null!");
|
||||
|
||||
return propertyToFieldName.get(property);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fieldName must not be empty or {@literal null}.
|
||||
* @return {@literal true} if the field name resolves to a {@literal PersistentProperty}.
|
||||
*/
|
||||
public boolean hasPersistentPropertyForField(String fieldName) {
|
||||
|
||||
Assert.hasText(fieldName, "Field name must not be null or empty!");
|
||||
|
||||
return fieldNameToProperty.containsKey(fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fieldName must not be empty or {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public PersistentProperty<?> getPersistentProperty(String fieldName) {
|
||||
|
||||
Assert.hasText(fieldName, "Field name must not be null or empty!");
|
||||
|
||||
return fieldNameToProperty.get(fieldName);
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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.springframework.data.rest.webmvc.json;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.rest.webmvc.support.DefaultedPageable;
|
||||
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* {@link HandlerMethodArgumentResolver} to resolve {@link DefaultedPageable} from a
|
||||
* {@link PageableHandlerMethodArgumentResolver} applying field to property mapping.
|
||||
* <p>
|
||||
* A resolved {@link DefaultedPageable} is post-processed by applying Jackson field-to-property mapping if it contains a
|
||||
* {@link Sort} instance. Customized fields are resolved to their property names. Unknown properties are removed from
|
||||
* {@link Sort}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Oliver Gierke
|
||||
* @since 2.6, 2.5.3, 2.4.5
|
||||
*/
|
||||
public class MappingAwareDefaultedPageableArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final PageableHandlerMethodArgumentResolver delegate;
|
||||
private final JacksonMappingAwareSortTranslator translator;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MappingAwareDefaultedPageableArgumentResolver} for the given
|
||||
* {@link JacksonMappingAwareSortTranslator} and {@link PageableHandlerMethodArgumentResolver}.
|
||||
*
|
||||
* @param translator must not be {@literal null}.
|
||||
* @param delegate must not be {@literal null}.
|
||||
*/
|
||||
public MappingAwareDefaultedPageableArgumentResolver(JacksonMappingAwareSortTranslator translator,
|
||||
PageableHandlerMethodArgumentResolver delegate) {
|
||||
|
||||
Assert.notNull(translator, "JacksonMappingSortTranslator must not be null!");
|
||||
Assert.notNull(delegate, "PageableHandlerMethodArgumentResolver must not be null!");
|
||||
|
||||
this.translator = translator;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return DefaultedPageable.class.isAssignableFrom(parameter.getParameterType());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory)
|
||||
*/
|
||||
@Override
|
||||
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
|
||||
|
||||
Pageable pageable = delegate.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
|
||||
|
||||
if (pageable == null || pageable.getSort() == null) {
|
||||
return new DefaultedPageable(pageable, delegate.isFallbackPageable(pageable));
|
||||
}
|
||||
|
||||
Sort translated = translator.translateSort(pageable.getSort(), parameter, webRequest);
|
||||
pageable = new PageRequest(pageable.getPageNumber(), pageable.getPageSize(), translated);
|
||||
|
||||
return new DefaultedPageable(pageable, delegate.isFallbackPageable(pageable));
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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.springframework.data.rest.webmvc.json;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* {@link HandlerMethodArgumentResolver} to resolve {@link Pageable} from a
|
||||
* {@link PageableHandlerMethodArgumentResolver} applying field to property mapping.
|
||||
* <p>
|
||||
* A resolved {@link Pageable} is post-processed by applying Jackson field-to-property mapping if it contains a
|
||||
* {@link Sort} instance. Customized fields are resolved to their property names. Unknown properties are removed from
|
||||
* {@link Sort}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Oliver Gierke
|
||||
* @since 2.6, 2.5.3, 2.4.5
|
||||
*/
|
||||
public class MappingAwarePageableArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final JacksonMappingAwareSortTranslator translator;
|
||||
private final PageableHandlerMethodArgumentResolver delegate;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MappingAwarePageableArgumentResolver} for the given {@link JacksonMappingAwareSortTranslator}
|
||||
* and {@link PageableHandlerMethodArgumentResolver}.
|
||||
*
|
||||
* @param translator must not be {@literal null}.
|
||||
* @param delegate must not be {@literal null}.
|
||||
*/
|
||||
public MappingAwarePageableArgumentResolver(JacksonMappingAwareSortTranslator translator,
|
||||
PageableHandlerMethodArgumentResolver delegate) {
|
||||
|
||||
Assert.notNull(translator, "JacksonMappingSortTranslator must not be null!");
|
||||
Assert.notNull(delegate, "PageableHandlerMethodArgumentResolver must not be null!");
|
||||
|
||||
this.translator = translator;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return delegate.supportsParameter(parameter);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory)
|
||||
*/
|
||||
@Override
|
||||
public Pageable resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
|
||||
|
||||
Pageable pageable = delegate.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
|
||||
|
||||
if (pageable == null || pageable.getSort() == null) {
|
||||
return pageable;
|
||||
}
|
||||
|
||||
Sort translated = translator.translateSort(pageable.getSort(), parameter, webRequest);
|
||||
return new PageRequest(pageable.getPageNumber(), pageable.getPageSize(), translated);
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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.springframework.data.rest.webmvc.json;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* {@link HandlerMethodArgumentResolver} to resolve {@link Sort} from a {@link SortHandlerMethodArgumentResolver}
|
||||
* applying field to property mapping.
|
||||
* <p>
|
||||
* A resolved {@link Sort} is post-processed by applying Jackson field-to-property mapping. Customized fields are
|
||||
* resolved to their property names. Unknown properties are removed from {@link Sort}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Oliver Gierke
|
||||
* @since 2.6, 2.5.3, 2.4.5
|
||||
*/
|
||||
public class MappingAwareSortArgumentResolver implements HandlerMethodArgumentResolver {
|
||||
|
||||
private final JacksonMappingAwareSortTranslator translator;
|
||||
private final SortHandlerMethodArgumentResolver delegate;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MappingAwareSortArgumentResolver} for the given {@link JacksonMappingAwareSortTranslator} and
|
||||
* {@link SortHandlerMethodArgumentResolver}.
|
||||
*
|
||||
* @param translator must not be {@literal null}.
|
||||
* @param delegate must not be {@literal null}.
|
||||
*/
|
||||
public MappingAwareSortArgumentResolver(JacksonMappingAwareSortTranslator translator,
|
||||
SortHandlerMethodArgumentResolver delegate) {
|
||||
|
||||
Assert.notNull(translator, "JacksonMappingSortTranslator must not be null!");
|
||||
Assert.notNull(delegate, "PageableHandlerMethodArgumentResolver must not be null!");
|
||||
|
||||
this.translator = translator;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter)
|
||||
*/
|
||||
@Override
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
return delegate.supportsParameter(parameter);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory)
|
||||
*/
|
||||
@Override
|
||||
public Sort resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
|
||||
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
|
||||
|
||||
Sort sort = delegate.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
|
||||
|
||||
return sort == null ? null : translator.translateSort(sort, parameter, webRequest);
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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.springframework.data.rest.webmvc.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMetadata;
|
||||
import org.springframework.data.rest.webmvc.BaseUri;
|
||||
import org.springframework.data.rest.webmvc.util.UriUtils;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
|
||||
/**
|
||||
* Resolves a domain class from a web request. Domain class resolution is only available for {@link NativeWebRequest web
|
||||
* requests} related to mapped and exported {@link Repositories}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Oliver Gierke
|
||||
* @since 2.6, 2.5.3, 2.4.5
|
||||
*/
|
||||
public class DomainClassResolver {
|
||||
|
||||
private final Repositories repositories;
|
||||
private final ResourceMappings mappings;
|
||||
private final BaseUri baseUri;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DomainClassResolver} for the given {@link Repositories} and {@link ResourceMappings}.
|
||||
*
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param mappings must not be {@literal null}.
|
||||
* @param baseUri must not be {@literal null}.
|
||||
*/
|
||||
private DomainClassResolver(Repositories repositories, ResourceMappings mappings, BaseUri baseUri) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(mappings, "ResourceMappings must not be null!");
|
||||
Assert.notNull(baseUri, "BaseUri must not be null!");
|
||||
|
||||
this.repositories = repositories;
|
||||
this.mappings = mappings;
|
||||
this.baseUri = baseUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link DomainClassResolver} for the given {@link Repositories} and {@link ResourceMappings}.
|
||||
*
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param mappings must not be {@literal null}.
|
||||
* @param baseUri must not be {@literal null}.
|
||||
*/
|
||||
public static DomainClassResolver of(Repositories repositories, ResourceMappings mappings, BaseUri baseUri) {
|
||||
return new DomainClassResolver(repositories, mappings, baseUri);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a domain class that is associated with the {@link NativeWebRequest}
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param webRequest must not be {@literal null}.
|
||||
* @return domain type that is associated with this request.
|
||||
* @throws IllegalArgumentException if there's no repository key associated or no domain type can be resolved.
|
||||
*/
|
||||
public Class<?> resolve(Method method, NativeWebRequest webRequest) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
Assert.notNull(webRequest, "NativeWebRequest must not be null!");
|
||||
|
||||
String lookupPath = baseUri.getRepositoryLookupPath(webRequest);
|
||||
String repositoryKey = UriUtils.findMappingVariable("repository", method, lookupPath);
|
||||
|
||||
if (!StringUtils.hasText(repositoryKey)) {
|
||||
throw new IllegalArgumentException(String.format("Could not determine a repository key from %s.", lookupPath));
|
||||
}
|
||||
|
||||
for (Class<?> domainType : repositories) {
|
||||
|
||||
ResourceMetadata mapping = mappings.getMetadataFor(domainType);
|
||||
|
||||
if (mapping.getPath().matches(repositoryKey) && mapping.isExported()) {
|
||||
return domainType;
|
||||
}
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(
|
||||
String.format("Could not resolve an exported domain type for %s.", repositoryKey));
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,8 @@ import javax.persistence.ManyToMany;
|
||||
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Entity
|
||||
public class Book {
|
||||
@@ -41,18 +38,14 @@ public class Book {
|
||||
@ManyToMany(cascade = { CascadeType.MERGE })//
|
||||
@RestResource(path = "creators") public Set<Author> authors;
|
||||
|
||||
@JsonProperty("sales")
|
||||
public long soldUnits;
|
||||
|
||||
public String title;
|
||||
|
||||
protected Book() {}
|
||||
|
||||
public Book(String isbn, String title, long soldUnits, Iterable<Author> authors) {
|
||||
public Book(String isbn, String title, Iterable<Author> authors) {
|
||||
|
||||
this.isbn = isbn;
|
||||
this.title = title;
|
||||
this.soldUnits = soldUnits;
|
||||
|
||||
this.authors = new HashSet<Author>();
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@ package org.springframework.data.rest.webmvc.jpa;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.jpa.repository.Query;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
@@ -28,7 +26,6 @@ import org.springframework.data.rest.core.annotation.RestResource;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RepositoryRestResource(excerptProjection = BookExcerpt.class)
|
||||
public interface BookRepository extends CrudRepository<Book, Long> {
|
||||
@@ -38,8 +35,4 @@ public interface BookRepository extends CrudRepository<Book, Long> {
|
||||
|
||||
@Query("select b from Book b where :author member of b.authors")
|
||||
List<Book> findByAuthorsContains(@Param("author") Author author);
|
||||
|
||||
@RestResource(rel = "find-spring-books-sorted")
|
||||
@Query("select b from Book b where b.title like 'Spring%'")
|
||||
Page<Book> findByTitleIsLike(Pageable pageable);
|
||||
}
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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.springframework.data.rest.webmvc.jpa;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageImpl;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.webmvc.AbstractWebIntegrationTests;
|
||||
import org.springframework.data.rest.webmvc.RepositoryRestController;
|
||||
import org.springframework.data.rest.webmvc.config.RepositoryRestConfigurerAdapter;
|
||||
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.test.context.web.WebAppConfiguration;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
/**
|
||||
* Web integration tests specific to default {@link Pageable} handling.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@WebAppConfiguration
|
||||
@ContextConfiguration(classes = JpaDefaultPageableWebTests.Config.class)
|
||||
public class JpaDefaultPageableWebTests extends AbstractWebIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ RepositoryRestMvcConfiguration.class, JpaRepositoryConfig.class })
|
||||
@EnableJpaRepositories(considerNestedRepositories = true)
|
||||
static class Config extends RepositoryRestConfigurerAdapter {
|
||||
|
||||
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
|
||||
config.setDefaultPageSize(1);
|
||||
}
|
||||
}
|
||||
|
||||
@RepositoryRestController
|
||||
public static class MyRestController {
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET, value = "books/default-pageable")
|
||||
@ResponseBody
|
||||
Page<Book> getDefaultPageable(Pageable pageable) {
|
||||
|
||||
if (pageable != null) {
|
||||
return new PageImpl<Book>(Collections.singletonList(new Book()), pageable, 1);
|
||||
}
|
||||
|
||||
return new PageImpl<Book>(Collections.<Book>emptyList(), pageable, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired TestDataPopulator loader;
|
||||
@Autowired ApplicationContext context;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
loader.populateRepositories();
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-906
|
||||
*/
|
||||
@Test
|
||||
public void executesSearchThatTakesAMappedSortProperty() throws Exception {
|
||||
|
||||
Link findBySortedLink = client.discoverUnique("books", "search", "find-spring-books-sorted");
|
||||
|
||||
// Assert sort options advertised
|
||||
assertThat(findBySortedLink.isTemplated(), is(true));
|
||||
assertThat(findBySortedLink.getVariableNames(), hasItems("sort", "projection"));
|
||||
|
||||
// Assert results returned as specified
|
||||
client.follow(findBySortedLink.expand()).//
|
||||
andExpect(jsonPath("$._embedded.books[0].title").exists()).//
|
||||
andExpect(jsonPath("$._embedded.books[1].title").doesNotExist());
|
||||
|
||||
client.follow(findBySortedLink.expand("sales,desc")).//
|
||||
andExpect(jsonPath("$._embedded.books[0].title").exists()).//
|
||||
andExpect(jsonPath("$._embedded.books[1].title").doesNotExist());
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-906
|
||||
*/
|
||||
@Test
|
||||
public void shouldApplyDefaultPageable() throws Exception {
|
||||
|
||||
mvc.perform(get("/books/default-pageable")).andDo(print()) //
|
||||
.andExpect(jsonPath("$.content[0].sales").value(0)) //
|
||||
.andExpect(jsonPath("$.size").value(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-906
|
||||
*/
|
||||
@Test
|
||||
public void shouldOverrideDefaultPageable() throws Exception {
|
||||
|
||||
mvc.perform(get("/books/default-pageable?size=10")).andDo(print()) //
|
||||
.andExpect(jsonPath("$.content[0].sales").value(0)) //
|
||||
.andExpect(jsonPath("$.size").value(10));
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,6 @@ import com.jayway.jsonpath.JsonPath;
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @author Greg Turnquist
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Transactional
|
||||
@ContextConfiguration(classes = JpaRepositoryConfig.class)
|
||||
@@ -547,7 +546,7 @@ public class JpaWebTests extends CommonWebTests {
|
||||
* @see DATAREST-384
|
||||
*/
|
||||
@Test
|
||||
public void exectuesSearchThatTakesASort() throws Exception {
|
||||
public void execturesSearchThatTakesASort() throws Exception {
|
||||
|
||||
Link booksLink = client.discoverUnique("books");
|
||||
Link searchLink = client.discoverUnique(booksLink, "search");
|
||||
@@ -655,53 +654,6 @@ public class JpaWebTests extends CommonWebTests {
|
||||
assertThat(links.hasLink("person"), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-883
|
||||
*/
|
||||
@Test
|
||||
public void exectuesSearchThatTakesAMappedSortProperty() throws Exception {
|
||||
|
||||
Link findBySortedLink = client.discoverUnique("books", "search", "find-by-sorted");
|
||||
|
||||
// Assert sort options advertised
|
||||
assertThat(findBySortedLink.isTemplated(), is(true));
|
||||
assertThat(findBySortedLink.getVariableNames(), hasItems("sort", "projection"));
|
||||
|
||||
// Assert results returned as specified
|
||||
client.follow(findBySortedLink.expand("sales,desc")).//
|
||||
andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data (Second Edition)")).//
|
||||
andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data")).//
|
||||
andExpect(client.hasLinkWithRel("self"));
|
||||
|
||||
client.follow(findBySortedLink.expand("sales,asc")).//
|
||||
andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data")).//
|
||||
andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data (Second Edition)")).//
|
||||
andExpect(client.hasLinkWithRel("self"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-883
|
||||
*/
|
||||
@Test
|
||||
public void exectuesCustomQuerySearchThatTakesAMappedSortProperty() throws Exception {
|
||||
|
||||
Link findByLink = client.discoverUnique("books", "search", "find-spring-books-sorted");
|
||||
|
||||
// Assert sort options advertised
|
||||
assertThat(findByLink.isTemplated(), is(true));
|
||||
|
||||
// Assert results returned as specified
|
||||
client.follow(findByLink.expand("0", "10", "sales,desc")).//
|
||||
andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data (Second Edition)")).//
|
||||
andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data")).//
|
||||
andExpect(client.hasLinkWithRel("self"));
|
||||
|
||||
client.follow(findByLink.expand("0", "10", "unknown,asc,sales,asc")).//
|
||||
andExpect(jsonPath("$._embedded.books[0].title").value("Spring Data")).//
|
||||
andExpect(jsonPath("$._embedded.books[1].title").value("Spring Data (Second Edition)")).//
|
||||
andExpect(client.hasLinkWithRel("self"));
|
||||
}
|
||||
|
||||
private List<Link> preparePersonResources(Person primary, Person... persons) throws Exception {
|
||||
|
||||
Link peopleLink = client.discoverUnique("people");
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
/**
|
||||
* @author Jon Brisbin
|
||||
* @author Oliver Gierke
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class TestDataPopulator {
|
||||
|
||||
@@ -54,8 +53,8 @@ public class TestDataPopulator {
|
||||
|
||||
Iterable<Author> authors = this.authors.save(Arrays.asList(ollie, mark, michael, david, john, thomas));
|
||||
|
||||
books.save(new Book("1449323952", "Spring Data", 1000, authors));
|
||||
books.save(new Book("1449323953", "Spring Data (Second Edition)", 2000, authors));
|
||||
books.save(new Book("1449323952", "Spring Data", authors));
|
||||
books.save(new Book("1449323953", "Spring Data (Second Edition)", authors));
|
||||
}
|
||||
|
||||
private void populateOrders() {
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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.springframework.data.rest.webmvc.json;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.*;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.domain.Sort.Direction;
|
||||
import org.springframework.data.web.PageableHandlerMethodArgumentResolver;
|
||||
import org.springframework.web.bind.support.WebDataBinderFactory;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MappingAwarePageableArgumentResolver}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class MappingAwarePageableArgumentResolverUnitTests {
|
||||
|
||||
@Mock JacksonMappingAwareSortTranslator translator;
|
||||
@Mock PageableHandlerMethodArgumentResolver delegate;
|
||||
@Mock MethodParameter parameter;
|
||||
@Mock NativeWebRequest webRequest;
|
||||
@Mock ModelAndViewContainer modelAndViewContainer;
|
||||
@Mock WebDataBinderFactory binderFactory;
|
||||
|
||||
MappingAwarePageableArgumentResolver resolver;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
resolver = new MappingAwarePageableArgumentResolver(translator, delegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-906
|
||||
*/
|
||||
@Test
|
||||
public void resolveArgumentShouldReturnTranslatedPageable() throws Exception {
|
||||
|
||||
Sort translated = new Sort("world");
|
||||
Pageable pageable = new PageRequest(0, 1, Direction.ASC, "hello");
|
||||
|
||||
when(delegate.resolveArgument(parameter, modelAndViewContainer, webRequest, binderFactory)).thenReturn(pageable);
|
||||
when(translator.translateSort(pageable.getSort(), parameter, webRequest)).thenReturn(translated);
|
||||
|
||||
Pageable result = resolver.resolveArgument(parameter, modelAndViewContainer, webRequest, binderFactory);
|
||||
|
||||
assertThat(result.getPageSize(), is(1));
|
||||
assertThat(result.getPageNumber(), is(0));
|
||||
assertThat(result.getSort(), is(equalTo(translated)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-906
|
||||
*/
|
||||
@Test
|
||||
public void resolveArgumentShouldReturnPageableWithoutSort() throws Exception {
|
||||
|
||||
Pageable pageable = new PageRequest(0, 1);
|
||||
|
||||
when(delegate.resolveArgument(parameter, modelAndViewContainer, webRequest, binderFactory)).thenReturn(pageable);
|
||||
|
||||
Pageable result = resolver.resolveArgument(parameter, modelAndViewContainer, webRequest, binderFactory);
|
||||
|
||||
assertThat(result.getPageSize(), is(1));
|
||||
assertThat(result.getPageNumber(), is(0));
|
||||
assertThat(result.getSort(), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-906
|
||||
*/
|
||||
@Test
|
||||
public void resolveArgumentShouldReturnNoPageable() throws Exception {
|
||||
|
||||
Pageable result = resolver.resolveArgument(parameter, modelAndViewContainer, webRequest, binderFactory);
|
||||
|
||||
assertThat(result, is(nullValue()));
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 the original author or authors.
|
||||
*
|
||||
* 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.springframework.data.rest.webmvc.json;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link JacksonMappingAwareSortTranslator.SortTranslator}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Oliver Gierke
|
||||
* @soundtrack dkn - Out Of This World (original version)
|
||||
*/
|
||||
public class SortTranslatorUnitTests {
|
||||
|
||||
private MongoMappingContext mappingContext;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
mappingContext = new MongoMappingContext();
|
||||
mappingContext.getPersistentEntity(Plain.class);
|
||||
mappingContext.getPersistentEntity(WithJsonProperty.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-883
|
||||
*/
|
||||
@Test
|
||||
public void shouldMapKnownProperties() {
|
||||
|
||||
MappedProperties mappedProperties = MappedProperties
|
||||
.fromJacksonProperties(mappingContext.getPersistentEntity(Plain.class), new ObjectMapper());
|
||||
Sort translatedSort = new JacksonMappingAwareSortTranslator.SortTranslator(mappedProperties)
|
||||
.translateSort(new Sort("hello", "name"));
|
||||
|
||||
assertThat(translatedSort.getOrderFor("hello"), is(nullValue()));
|
||||
assertThat(translatedSort.getOrderFor("name"), is(notNullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-883
|
||||
*/
|
||||
@Test
|
||||
public void returnsNullSortIfNoPropertiesMatch() {
|
||||
|
||||
MappedProperties mappedProperties = MappedProperties
|
||||
.fromJacksonProperties(mappingContext.getPersistentEntity(Plain.class), new ObjectMapper());
|
||||
Sort translatedSort = new JacksonMappingAwareSortTranslator.SortTranslator(mappedProperties)
|
||||
.translateSort(new Sort("hello", "world"));
|
||||
|
||||
assertThat(translatedSort, is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-883
|
||||
*/
|
||||
@Test
|
||||
public void shouldMapKnownPropertiesWithJsonProperty() {
|
||||
|
||||
MappedProperties mappedProperties = MappedProperties
|
||||
.fromJacksonProperties(mappingContext.getPersistentEntity(WithJsonProperty.class), new ObjectMapper());
|
||||
Sort translatedSort = new JacksonMappingAwareSortTranslator.SortTranslator(mappedProperties)
|
||||
.translateSort(new Sort("hello", "foo"));
|
||||
|
||||
assertThat(translatedSort.getOrderFor("hello"), is(nullValue()));
|
||||
assertThat(translatedSort.getOrderFor("name"), is(notNullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-883
|
||||
*/
|
||||
@Test
|
||||
public void shouldJacksonFieldNameForMapping() {
|
||||
|
||||
MappedProperties mappedProperties = MappedProperties
|
||||
.fromJacksonProperties(mappingContext.getPersistentEntity(WithJsonProperty.class), new ObjectMapper());
|
||||
Sort translatedSort = new JacksonMappingAwareSortTranslator.SortTranslator(mappedProperties)
|
||||
.translateSort(new Sort("name"));
|
||||
|
||||
assertThat(translatedSort, is(nullValue()));
|
||||
}
|
||||
|
||||
static class Plain {
|
||||
public String name;
|
||||
}
|
||||
|
||||
static class WithJsonProperty {
|
||||
public @JsonProperty("foo") String name;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user