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:
Mark Paluch
2016-09-27 08:59:56 +02:00
parent 5e163d1913
commit 0a49b099bb
15 changed files with 76 additions and 962 deletions

View File

@@ -90,11 +90,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.PersistentEntityJackson2Module.LookupObjectSerializer;
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.NestedEntitySerializer;
@@ -106,8 +102,8 @@ import org.springframework.data.rest.webmvc.spi.BackendIdConverter;
import org.springframework.data.rest.webmvc.spi.BackendIdConverter.DefaultIdConverter;
import org.springframework.data.rest.webmvc.support.BackendIdHandlerMethodArgumentResolver;
import org.springframework.data.rest.webmvc.support.DefaultExcerptProjector;
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.ExcerptProjector;
import org.springframework.data.rest.webmvc.support.HttpMethodHandlerMethodArgumentResolver;
@@ -161,12 +157,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 {
@@ -786,17 +781,10 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
associationLinks());
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());

View File

@@ -19,6 +19,7 @@ import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
@@ -32,8 +33,12 @@ import org.springframework.data.rest.webmvc.mapping.Associations;
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;
/**
@@ -42,7 +47,6 @@ 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
*/
@RequiredArgsConstructor
@@ -50,11 +54,12 @@ public class DomainObjectReader {
private final @NonNull PersistentEntities entities;
private final @NonNull Associations associationLinks;
private final @NonNull ClassIntrospector 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
@@ -92,7 +97,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 +156,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();) {
@@ -236,4 +241,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);
}
}
}

View File

@@ -1,109 +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 lombok.NonNull;
import lombok.RequiredArgsConstructor;
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
*/
@RequiredArgsConstructor
public class JacksonMappingAwareSortTranslator {
private final @NonNull ObjectMapper objectMapper;
private final @NonNull Repositories repositories;
private final @NonNull 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
*/
@RequiredArgsConstructor
static class SortTranslator {
private final @NonNull 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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -1,78 +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 lombok.NonNull;
import lombok.RequiredArgsConstructor;
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.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
*/
@RequiredArgsConstructor
public class MappingAwareDefaultedPageableArgumentResolver implements HandlerMethodArgumentResolver {
private final @NonNull JacksonMappingAwareSortTranslator translator;
private final @NonNull PageableHandlerMethodArgumentResolver 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));
}
}

View File

@@ -1,76 +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 lombok.NonNull;
import lombok.RequiredArgsConstructor;
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.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
*/
@RequiredArgsConstructor
public class MappingAwarePageableArgumentResolver implements HandlerMethodArgumentResolver {
private final @NonNull JacksonMappingAwareSortTranslator translator;
private final @NonNull PageableHandlerMethodArgumentResolver 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);
}
}

View File

@@ -1,67 +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 lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.SortHandlerMethodArgumentResolver;
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
*/
@RequiredArgsConstructor
public class MappingAwareSortArgumentResolver implements HandlerMethodArgumentResolver {
private final @NonNull JacksonMappingAwareSortTranslator translator;
private final @NonNull SortHandlerMethodArgumentResolver 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);
}
}

View File

@@ -1,79 +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 lombok.NonNull;
import lombok.RequiredArgsConstructor;
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
*/
@RequiredArgsConstructor(staticName = "of")
public class DomainClassResolver {
private final @NonNull Repositories repositories;
private final @NonNull ResourceMappings mappings;
private final @NonNull BaseUri 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));
}
}

View File

@@ -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()));
}
}

View File

@@ -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.keyvalue.core.mapping.context.KeyValueMappingContext;
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 KeyValueMappingContext mappingContext;
@Before
public void setUp() {
mappingContext = new KeyValueMappingContext();
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;
}
}