DATAREST-93 - Further refactorings and refinements regarding the search resource mapping.

This commit is contained in:
Oliver Gierke
2013-07-12 14:20:32 +02:00
parent f8e53058f8
commit a4d8a22428
20 changed files with 332 additions and 122 deletions

View File

@@ -56,7 +56,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
* @author Jon Brisbin
* @author Oliver Gierke
*/
@SuppressWarnings({ "rawtypes", "deprecation" })
@SuppressWarnings({ "rawtypes" })
class AbstractRepositoryRestController implements MessageSourceAware, InitializingBean {
private static final Logger LOG = LoggerFactory.getLogger(AbstractRepositoryRestController.class);
@@ -196,11 +196,12 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi
}
protected Link resourceLink(RepositoryRestRequest repoRequest, Resource resource) {
ResourceMetadata repoMapping = repoRequest.getRepositoryResourceMapping();
ResourceMetadata entityMapping = repoRequest.getPersistentEntityResourceMapping();
Link selfLink = resource.getLink("self");
String rel = repoMapping.getRel() + "." + entityMapping.getRel();
String rel = repoMapping.getSingleResourceRel();
return new Link(selfLink.getHref(), rel);
}

View File

@@ -35,7 +35,6 @@ import org.springframework.data.mapping.model.BeanWrapper;
import org.springframework.data.repository.support.DomainClassConverter;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.PersistentEntityResource;
import org.springframework.data.rest.repository.context.AfterCreateEvent;
import org.springframework.data.rest.repository.context.AfterDeleteEvent;
@@ -45,6 +44,7 @@ import org.springframework.data.rest.repository.context.BeforeDeleteEvent;
import org.springframework.data.rest.repository.context.BeforeSaveEvent;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.repository.mapping.SearchResourceMappings;
import org.springframework.data.rest.repository.support.DomainObjectMerger;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityLinks;
@@ -69,7 +69,6 @@ import org.springframework.web.bind.annotation.ResponseBody;
* @author Oliver Gierke
*/
@RestController
@SuppressWarnings("deprecation")
class RepositoryEntityController extends AbstractRepositoryRestController implements ApplicationEventPublisherAware {
private static final String BASE_MAPPING = "/{repository}";
@@ -112,15 +111,16 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
this.publisher = publisher;
}
@ResponseBody
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = { "application/json",
"application/x-spring-data-verbose+json" })
@ResponseBody
public Resources<?> listEntities(final RepositoryRestRequest request, Pageable pageable)
throws ResourceNotFoundException {
List<Link> links = new ArrayList<Link>();
List<Link> links = new ArrayList<Link>();
Iterable<?> results;
RepositoryMethodInvoker repoMethodInvoker = request.getRepositoryMethodInvoker();
if (null == repoMethodInvoker) {
throw new ResourceNotFoundException();
}
@@ -136,9 +136,12 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
}
ResourceMetadata repoMapping = request.getRepositoryResourceMapping();
if (!repoMethodInvoker.getQueryMethods().isEmpty()) {
links.add(entityLinks.linkForSingleResource(request.getPersistentEntity().getType(), "search").withRel(
repoMapping.getRel() + ".search"));
SearchResourceMappings searchMappings = repoMapping.getSearchResourceMappings();
if (searchMappings.isExported()) {
links.add(entityLinks.linkFor(repoMapping.getDomainType()).slash(searchMappings.getPath())
.withRel(searchMappings.getRel()));
}
Resources<?> resources = resultToResources(results);
@@ -270,10 +273,13 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem
&& !(repoMethodInvoker.hasDeleteOne() || repoMethodInvoker.hasDeleteOneById())) {
throw new HttpRequestMethodNotSupportedException("DELETE");
}
ResourceMapping methodMapping = repoRequest.getRepositoryResourceMapping().getResourceMappingFor("delete");
if (null != methodMapping && !methodMapping.isExported()) {
throw new HttpRequestMethodNotSupportedException("DELETE");
}
// TODO: re-enable not exposing delete method if hidden
// ResourceMapping methodMapping = repoRequest.getRepositoryResourceMapping().getResourceMappingFor("delete");
// if (null != methodMapping && !methodMapping.isExported()) {
// throw new HttpRequestMethodNotSupportedException("DELETE");
// }
final Object domainObj = converter.convert(id, STRING_TYPE,
TypeDescriptor.valueOf(repoRequest.getPersistentEntity().getType()));

View File

@@ -44,6 +44,7 @@ import org.springframework.data.rest.repository.context.AfterLinkSaveEvent;
import org.springframework.data.rest.repository.context.BeforeLinkDeleteEvent;
import org.springframework.data.rest.repository.context.BeforeLinkSaveEvent;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.Resource;
@@ -115,7 +116,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
List<Resource<?>> resources = new ArrayList<Resource<?>>();
for (Object obj : ((Iterable<Object>) prop.propertyValue)) {
for (Object obj : (Iterable<Object>) prop.propertyValue) {
resources.add(perAssembler.toResource(obj));
}
@@ -198,7 +199,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
throw new ResourceNotFoundException();
}
if (prop.property.isCollectionLike()) {
for (Object obj : ((Iterable<?>) prop.propertyValue)) {
for (Object obj : (Iterable<?>) prop.propertyValue) {
BeanWrapper<?, Object> propValWrapper = BeanWrapper.create(obj, null);
String sId = propValWrapper.getProperty(prop.entity.getIdProperty()).toString();
@@ -244,22 +245,19 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
return response;
}
ResourceMapping repoMapping = repoRequest.getRepositoryResourceMapping();
ResourceMapping entityMapping = repoRequest.getPersistentEntityResourceMapping();
String propName = entityMapping.getNameForPath(property);
ResourceMapping propMapping = entityMapping.getResourceMappingFor(entityMapping.getNameForPath(property));
PersistentProperty<?> persistentProp = repoRequest.getPersistentEntity().getPersistentProperty(propName);
Class<?> propType = (persistentProp.isCollectionLike() || persistentProp.isMap() ? persistentProp
.getComponentType() : persistentProp.getType());
ResourceMetadata repoMapping = repoRequest.getRepositoryResourceMapping();
PersistentProperty<?> persistentProp = repoRequest.getPersistentEntity().getPersistentProperty(property);
Class<?> propType = persistentProp.isCollectionLike() || persistentProp.isMap() ? persistentProp.getComponentType()
: persistentProp.getType();
ResourceMapping propRepoMapping = getResourceMapping(config, repositories.getRepositoryInformationFor(propType));
String propRel = String.format("%s.%s.%s.%s", repoMapping.getRel(), entityMapping.getRel(),
(null != propMapping ? propMapping.getRel() : property), propRepoMapping.getRel());
String propRel = String.format("%s.%s.%s", repoMapping.getSingleResourceRel(), property, propRepoMapping.getRel());
Resource<?> resource = response.getBody();
List<Link> links = new ArrayList<Link>();
URI entityBaseUri = buildUri(repoRequest.getBaseUri(), repoMapping.getPath(), id, property);
URI entityBaseUri = buildUri(repoRequest.getBaseUri(), repoMapping.getPath().toString(), id, property);
if (resource.getContent() instanceof Iterable) {
for (Resource<?> res : (Iterable<Resource<?>>) resource.getContent()) {
@@ -410,8 +408,7 @@ public class RepositoryPropertyReferenceController extends AbstractRepositoryRes
throw new ResourceNotFoundException();
}
String propertyName = repoRequest.getPersistentEntityResourceMapping().getNameForPath(propertyPath);
PersistentProperty<?> prop = repoRequest.getPersistentEntity().getPersistentProperty(propertyName);
PersistentProperty<?> prop = repoRequest.getPersistentEntity().getPersistentProperty(propertyPath);
if (null == prop) {
throw new ResourceNotFoundException();
}

View File

@@ -15,43 +15,51 @@
*/
package org.springframework.data.rest.webmvc;
import java.io.Serializable;
import java.net.URI;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.convert.ConversionService;
import org.springframework.data.mapping.PersistentEntity;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.hateoas.Link;
/**
* @author Jon Brisbin
* @author Oliver Gierke
*/
@SuppressWarnings("deprecation")
class RepositoryRestRequest {
private final HttpServletRequest request;
private final URI baseUri;
private final ResourceMetadata resourceMetadata;
private final Link repoLink;
private final RepositoryMethodInvoker repoMethodInvoker;
private final PersistentEntity<?, ?> persistentEntity;
public RepositoryRestRequest(RepositoryRestConfiguration config, Repositories repositories,
HttpServletRequest request, URI baseUri, ResourceMetadata repoInfo, ConversionService conversionService) {
this.request = request;
this.baseUri = baseUri;
this.resourceMetadata = repoInfo;
if (resourceMetadata == null || !resourceMetadata.isExported()) {
this.repoLink = null;
this.repoMethodInvoker = null;
this.persistentEntity = null;
} else {
this.persistentEntity = repositories.getPersistentEntity(repoInfo.getDomainType());
Class<?> domainType = repoInfo.getDomainType();
CrudRepository<Object, Serializable> repositoryFor = repositories.getRepositoryFor(domainType);
RepositoryInformation information = repositories.getRepositoryInformationFor(domainType);
this.repoMethodInvoker = new RepositoryMethodInvoker(repositoryFor, information, conversionService);
this.persistentEntity = repositories.getPersistentEntity(domainType);
}
}

View File

@@ -67,6 +67,8 @@ public class RepositoryRestRequestHandlerMethodArgumentResolver implements Handl
URI baseUri = baseUriResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
ResourceMetadata repoInfo = repoInfoResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
// TODO reject if ResourceMetadata cannot be resolved
return new RepositoryRestRequest(config, repositories, webRequest.getNativeRequest(HttpServletRequest.class),
baseUri, repoInfo, conversionService);
}

View File

@@ -15,10 +15,7 @@
*/
package org.springframework.data.rest.webmvc;
import static org.springframework.data.rest.core.util.UriUtils.*;
import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*;
import static org.springframework.data.rest.webmvc.ControllerUtils.*;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.*;
import java.lang.reflect.Method;
import java.net.URI;
@@ -29,15 +26,14 @@ import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.config.RepositoryRestConfiguration;
import org.springframework.data.rest.config.ResourceMapping;
import org.springframework.data.rest.repository.invoke.RepositoryMethod;
import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker;
import org.springframework.data.rest.repository.support.ResourceMappingUtils;
import org.springframework.data.rest.webmvc.support.BaseUriLinkBuilder;
import org.springframework.data.rest.repository.mapping.ResourceMapping;
import org.springframework.data.rest.repository.mapping.ResourceMappings;
import org.springframework.data.rest.repository.mapping.ResourceMetadata;
import org.springframework.data.rest.repository.mapping.SearchResourceMappings;
import org.springframework.data.web.PagedResourcesAssembler;
import org.springframework.hateoas.EntityLinks;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.LinkBuilder;
import org.springframework.hateoas.Resource;
@@ -53,22 +49,21 @@ import org.springframework.web.bind.annotation.ResponseBody;
* @author Oliver Gierke
*/
@RestController
@SuppressWarnings("deprecation")
class RepositorySearchController extends AbstractRepositoryRestController {
private static final String BASE_MAPPING = "/{repository}/search";
private final Repositories repositories;
private final RepositoryRestConfiguration config;
private final EntityLinks entityLinks;
private final ResourceMappings mappings;
@Autowired
public RepositorySearchController(Repositories repositories, RepositoryRestConfiguration config,
PagedResourcesAssembler<Object> assembler, PersistentEntityResourceAssembler<Object> perAssembler) {
public RepositorySearchController(PagedResourcesAssembler<Object> assembler,
PersistentEntityResourceAssembler<Object> perAssembler, EntityLinks entityLinks, ResourceMappings mappings) {
super(assembler, perAssembler);
this.repositories = repositories;
this.config = config;
this.entityLinks = entityLinks;
this.mappings = mappings;
}
@RequestMapping(value = BASE_MAPPING, method = RequestMethod.GET, produces = { "application/json",
@@ -84,20 +79,19 @@ class RepositorySearchController extends AbstractRepositoryRestController {
}
protected List<Link> queryMethodLinks(URI baseUri, Class<?> domainType) {
List<Link> links = new ArrayList<Link>();
RepositoryInformation repoInfo = repositories.getRepositoryInformationFor(domainType);
ResourceMapping repoMapping = ResourceMappingUtils.merge(repoInfo.getRepositoryInterface(),
config.getResourceMappingForRepository(repoInfo.getRepositoryInterface()));
for (Method method : repoInfo.getQueryMethods()) {
LinkBuilder linkBuilder = BaseUriLinkBuilder.create(buildUri(baseUri, repoMapping.getPath(), "search"));
ResourceMapping methodMapping = ResourceMappingUtils.merge(method,
repoMapping.getResourceMappingFor(method.getName()));
if (!methodMapping.isExported()) {
LinkBuilder builder = entityLinks.linkFor(domainType).slash("search");
for (ResourceMapping mapping : mappings.getSearchResourceMappings(domainType)) {
if (!mapping.isExported()) {
continue;
}
links
.add(linkBuilder.slash(methodMapping.getPath()).withRel(repoMapping.getRel() + "." + methodMapping.getRel()));
links.add(builder.slash(mapping.getPath().toString()).withRel(mapping.getRel()));
}
return links;
}
@@ -106,34 +100,28 @@ class RepositorySearchController extends AbstractRepositoryRestController {
@ResponseBody
public ResourceSupport query(final RepositoryRestRequest repoRequest, @PathVariable String repository,
@PathVariable String method, Pageable pageable) throws ResourceNotFoundException {
RepositoryMethodInvoker repoMethodInvoker = repoRequest.getRepositoryMethodInvoker();
if (repoMethodInvoker.getQueryMethods().isEmpty()) {
throw new ResourceNotFoundException();
}
ResourceMapping repoMapping = repoRequest.getRepositoryResourceMapping();
String methodName = repoMapping.getNameForPath(method);
RepositoryMethod repoMethod = repoMethodInvoker.getQueryMethods().get(methodName);
if (null == repoMethod) {
for (RepositoryMethod queryMethod : repoMethodInvoker.getQueryMethods().values()) {
String path = findPath(queryMethod.getMethod());
if (path.equals(method)) {
repoMethod = queryMethod;
break;
}
}
if (null == repoMethod) {
throw new ResourceNotFoundException();
}
ResourceMetadata repoMapping = repoRequest.getRepositoryResourceMapping();
SearchResourceMappings searchResourceMappings = repoMapping.getSearchResourceMappings();
Method mappedMethod = searchResourceMappings.getMappedMethod(method);
if (mappedMethod == null) {
throw new ResourceNotFoundException();
}
RepositoryMethod repositoryMethod = new RepositoryMethod(mappedMethod);
Map<String, String[]> rawParameters = repoRequest.getRequest().getParameterMap();
Object result = repoMethodInvoker.invokeQueryMethod(repoMethod, pageable, rawParameters);
Object result = repoMethodInvoker.invokeQueryMethod(repositoryMethod, pageable, rawParameters);
Link baseLink = linkTo(methodOn(RepositorySearchController.class). //
queryCompact(repoRequest, repository, methodName, pageable)).withSelfRel();
return resultToResources(result, baseLink);
return resultToResources(result);
}
@RequestMapping(value = BASE_MAPPING + "/{method}", method = RequestMethod.GET,

View File

@@ -96,7 +96,7 @@ public class ResourceMetadataHandlerMethodArgumentResolver implements HandlerMet
for (Class<?> domainType : repositories) {
ResourceMetadata mapping = mappings.getMappingFor(domainType);
if (pathSegment.equals(mapping.getPath()) && mapping.isExported()) {
if (mapping.getPath().matches(pathSegment) && mapping.isExported()) {
return mapping;
}
}

View File

@@ -43,10 +43,10 @@ import org.springframework.data.rest.repository.support.DomainObjectMerger;
import org.springframework.data.rest.webmvc.BaseUriMethodArgumentResolver;
import org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler;
import org.springframework.data.rest.webmvc.PersistentEntityResourceHandlerMethodArgumentResolver;
import org.springframework.data.rest.webmvc.RepositoryInformationHandlerMethodArgumentResolver;
import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter;
import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping;
import org.springframework.data.rest.webmvc.RepositoryRestRequestHandlerMethodArgumentResolver;
import org.springframework.data.rest.webmvc.ResourceMetadataHandlerMethodArgumentResolver;
import org.springframework.data.rest.webmvc.RestController;
import org.springframework.data.rest.webmvc.ServerHttpRequestMethodArgumentResolver;
import org.springframework.data.rest.webmvc.convert.UriListHttpMessageConverter;
@@ -119,7 +119,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
@Bean
public UriDomainClassConverter uriDomainClassConverter() {
return new UriDomainClassConverter();
return new UriDomainClassConverter(repositories(), domainClassConverter());
}
/**
@@ -195,16 +195,6 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
return new BaseUriMethodArgumentResolver(config());
}
/**
* Resolves the {@link org.springframework.data.repository.core.RepositoryInformation} for this request.
*
* @return
*/
@Bean
public RepositoryInformationHandlerMethodArgumentResolver repoInfoMethodArgumentResolver() {
return new RepositoryInformationHandlerMethodArgumentResolver();
}
/**
* Turns an {@link javax.servlet.http.HttpServletRequest} into a
* {@link org.springframework.http.server.ServerHttpRequest}.
@@ -226,6 +216,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
return new RepositoryRestRequestHandlerMethodArgumentResolver(defaultConversionService());
}
@Bean
public ResourceMetadataHandlerMethodArgumentResolver resourceMetadataHandlerMethodArgumentResolver() {
return new ResourceMetadataHandlerMethodArgumentResolver(repositories(), resourceMappings());
}
/**
* A special {@link org.springframework.hateoas.EntityLinks} implementation that takes repository and current
* configuration into account when generating links.
@@ -258,7 +253,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
*/
@Bean
public PersistentEntityToJsonSchemaConverter jsonSchemaConverter() {
return new PersistentEntityToJsonSchemaConverter();
return new PersistentEntityToJsonSchemaConverter(repositories(), resourceMappings());
}
/**
@@ -391,8 +386,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
private List<HandlerMethodArgumentResolver> defaultMethodArgumentResolvers() {
return Arrays.asList(baseUriMethodArgumentResolver(), pageableResolver(), sortResolver(),
serverHttpRequestMethodArgumentResolver(), repoInfoMethodArgumentResolver(), repoRequestArgumentResolver(),
persistentEntityArgumentResolver());
serverHttpRequestMethodArgumentResolver(), repoRequestArgumentResolver(), persistentEntityArgumentResolver(),
resourceMetadataHandlerMethodArgumentResolver());
}
/**

View File

@@ -74,7 +74,7 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init
Assert.isTrue(persistentProperty.isAssociation(), "PersistentProperty must be an association!");
ResourceMetadata metadata = mappings.getMappingFor(persistentProperty.getOwner().getType());
if (!metadata.isManaged(persistentProperty)) {
if (!metadata.isManagedResource(persistentProperty)) {
return false;
}

View File

@@ -121,7 +121,7 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
PersistentProperty persistentProperty = association.getInverse();
if (!metadata.isMapped(persistentProperty)) {
if (!metadata.isExported(persistentProperty)) {
return;
}

View File

@@ -60,7 +60,7 @@ public class RepositoryLinkBuilder extends LinkBuilderSupport<RepositoryLinkBuil
String propName = property.getName();
if (metadata.isManaged(property)) {
if (metadata.isManagedResource(property)) {
return slash(metadata.getMappingFor(property).getPath());
} else {
return slash(propName);

View File

@@ -66,7 +66,7 @@ public class RepositoryTestsConfig {
@Bean
public UriDomainClassConverter uriDomainClassConverter() {
return new UriDomainClassConverter();
return new UriDomainClassConverter(repositories(), domainClassConverter());
}
@Bean