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

@@ -26,6 +26,7 @@ import org.springframework.util.StringUtils;
public class Path {
private static final String SLASH = "/";
private static final String MATCH_PATTERN = "/?%s";
private final String path;
@@ -48,6 +49,10 @@ public class Path {
this.path = cleanUp ? cleanUp(path) : path;
}
public boolean matches(String reference) {
return this.path.matches(String.format(MATCH_PATTERN, reference));
}
/**
* Appends the given {@link String} to the current {@link Path}.
*

View File

@@ -19,9 +19,10 @@ import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.springframework.data.rest.core.Path;
/**
* Unit tests for {@link Path}.
*
* @author Oliver Gierke
*/
public class PathUnitTests {
@@ -46,4 +47,19 @@ public class PathUnitTests {
Path builder = new Path("foo/ ").slash("/ b a r").slash(" //foobar/// ");
assertThat(builder.toString(), is("/foo/bar/foobar"));
}
@Test
public void matchesWithLeadingSlash() {
assertThat(new Path("/foobar").matches("/foobar"), is(true));
}
@Test
public void matchesWithoutLeadingSlash() {
assertThat(new Path("/foobar").matches("foobar"), is(true));
}
@Test
public void doesNotMatchIfDifferent() {
assertThat(new Path("/foobar").matches("barfoo"), is(false));
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2013 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.repository.mapping;
import java.lang.reflect.Method;
/**
* A {@link ResourceMapping} that is backed by a {@link Method}.
*
* @author Oliver Gierke
*/
public interface MethodResourceMapping extends ResourceMapping {
/**
* Returns the {@link Method} backing the resource.
*
* @return
*/
Method getMethod();
}

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.data.rest.repository.mapping;
import java.util.Map;
import org.springframework.context.annotation.Primary;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.mapping.PersistentProperty;
@@ -57,12 +55,23 @@ class RepositoryAwareResourceInformation implements ResourceMetadata {
return AnnotationUtils.findAnnotation(repositoryInterface.getRepositoryInterface(), Primary.class) != null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.ResourceMetadata#getDomainType()
*/
@Override
public Class<?> getDomainType() {
return repositoryInterface.getDomainType();
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.DelegatingResourceInformation#isManaged(org.springframework.data.mapping.PersistentProperty)
*/
@Override
public boolean isManaged(PersistentProperty<?> property) {
public boolean isManagedResource(PersistentProperty<?> property) {
Assert.notNull(property, "PersistentProperty must not be null!");
return repositories.hasRepositoryFor(property.getActualType());
}
@@ -80,7 +89,7 @@ class RepositoryAwareResourceInformation implements ResourceMetadata {
* @see org.springframework.data.rest.repository.mapping.ResourceMetadataProvider#hasMappingFor(org.springframework.data.mapping.PersistentProperty)
*/
@Override
public boolean isMapped(PersistentProperty<?> property) {
public boolean isExported(PersistentProperty<?> property) {
return provider.isMapped(property);
}
@@ -125,8 +134,7 @@ class RepositoryAwareResourceInformation implements ResourceMetadata {
* @see org.springframework.data.rest.repository.mapping.ResourceMetadata#getSearchResourceMappings()
*/
@Override
public Map<String, ResourceMapping> getSearchResourceMappings() {
public SearchResourceMappings getSearchResourceMappings() {
return provider.getSearchResourceMappings(repositoryInterface.getRepositoryInterface());
}
}

View File

@@ -20,6 +20,7 @@ import java.lang.reflect.Method;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.repository.annotation.RestResource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
@@ -27,19 +28,24 @@ import org.springframework.util.StringUtils;
*
* @author Oliver Gierke
*/
class RepositoryMethodResourceMapping implements ResourceMapping {
class RepositoryMethodResourceMapping implements MethodResourceMapping {
private final boolean isExported;
private final String rel;
private final Path path;
private final Method method;
/**
* Creates a new {@link RepositoryMethodResourceMapping} for the given {@link Method}.
*
* @param method must not be {@literal null}.
* @param resourceMapping must not be {@literal null}.
*/
public RepositoryMethodResourceMapping(Method method, ResourceMapping resourceMapping) {
Assert.notNull(method, "Method must not be null!");
Assert.notNull(resourceMapping, "ResourceMapping must not be null!");
RestResource annotation = AnnotationUtils.findAnnotation(method, RestResource.class);
this.isExported = annotation != null ? annotation.exported() : true;
@@ -49,6 +55,7 @@ class RepositoryMethodResourceMapping implements ResourceMapping {
String toAppend = annotation == null || !StringUtils.hasText(annotation.path()) ? method.getName() : annotation
.path();
this.path = resourcePath.slash(toAppend);
this.method = method;
}
/*
@@ -77,4 +84,13 @@ class RepositoryMethodResourceMapping implements ResourceMapping {
public Path getPath() {
return path;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.MethodResourceMapping#getMethod()
*/
@Override
public Method getMethod() {
return method;
}
}

View File

@@ -16,8 +16,10 @@
package org.springframework.data.rest.repository.mapping;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.springframework.data.mapping.PersistentProperty;
@@ -37,12 +39,11 @@ import org.springframework.util.Assert;
*/
public class ResourceMappings implements Iterable<ResourceMetadata> {
private final Repositories repositories;
private final RelProvider relProvider;
private final Map<Class<?>, ResourceMetadata> cache = new HashMap<Class<?>, ResourceMetadata>();
private final Map<Class<?>, Map<String, ResourceMapping>> searchCache = new HashMap<Class<?>, Map<String, ResourceMapping>>();
private final Map<Class<?>, SearchResourceMappings> searchCache = new HashMap<Class<?>, SearchResourceMappings>();
/**
* Creates a new {@link ResourceMappings} using the given {@link RepositoryRestConfiguration} and {@link Repositories}
@@ -109,10 +110,12 @@ public class ResourceMappings implements Iterable<ResourceMetadata> {
/**
* Returns the {@link ResourceMapping}s for the search resources of the given type.
*
* @param type
* @param type must not be {@literal null}.
* @return
*/
public Map<String, ResourceMapping> getSearchResourceMappings(Class<?> type) {
public SearchResourceMappings getSearchResourceMappings(Class<?> type) {
Assert.notNull(type, "Type must not be null!");
if (searchCache.containsKey(type)) {
return searchCache.get(type);
@@ -120,16 +123,16 @@ public class ResourceMappings implements Iterable<ResourceMetadata> {
Class<?> domainType = RepositoriesUtils.getDomainType(type);
RepositoryInformation repositoryInformation = repositories.getRepositoryInformationFor(domainType);
Map<String, ResourceMapping> mappings = new HashMap<String, ResourceMapping>();
List<MethodResourceMapping> mappings = new ArrayList<MethodResourceMapping>();
ResourceMetadata repositoryMapping = getMappingFor(repositoryInformation.getRepositoryInterface());
for (Method queryMethod : repositoryInformation.getQueryMethods()) {
mappings.put(queryMethod.getName(), new RepositoryMethodResourceMapping(queryMethod,
repositoryMapping));
mappings.add(new RepositoryMethodResourceMapping(queryMethod, repositoryMapping));
}
searchCache.put(type, mappings);
return mappings;
SearchResourceMappings searchMappings = new SearchResourceMappings(mappings);
searchCache.put(type, searchMappings);
return searchMappings;
}
/**

View File

@@ -15,20 +15,51 @@
*/
package org.springframework.data.rest.repository.mapping;
import java.util.Map;
import org.springframework.data.mapping.PersistentProperty;
/**
* Interface for metadata of resources exposed throught the system.
*
* @author Oliver Gierke
*/
public interface ResourceMetadata extends CollectionResourceMapping {
boolean isManaged(PersistentProperty<?> property);
boolean isMapped(PersistentProperty<?> property);
/**
* Returns the domain type that is exposed through the resource.
*
* @return
*/
Class<?> getDomainType();
/**
* Returns whether the type of the given {@link PersistentProperty} is exposed as resource itself.
*
* @param property must not be {@literal null}.
* @return
*/
boolean isManagedResource(PersistentProperty<?> property);
/**
* Returns whether the given {@link PersistentProperty} is a managed resource and in fact exported.
*
* @param property must not be {@literal null}.
* @return
*/
boolean isExported(PersistentProperty<?> property);
/**
* Returns the {@link ResourceMapping} for the given {@link PersistentProperty} or {@literal null} if not managed.
*
* @param property must not be {@literal null}.
* @return
*/
ResourceMapping getMappingFor(PersistentProperty<?> property);
Map<String, ResourceMapping> getSearchResourceMappings();
/**
* Returns the {@link SearchResourceMappings}, i.e. the mappings for the search resource exposed for the current
* resource.
*
* @return
*/
SearchResourceMappings getSearchResourceMappings();
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright 2013 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.repository.mapping;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import org.springframework.data.rest.core.Path;
import org.springframework.util.Assert;
/**
* {@link ResourceMapping} for all search resources.
*
* @author Oliver Gierke
*/
public class SearchResourceMappings implements Iterable<MethodResourceMapping>, ResourceMapping {
private static final Path PATH = new Path("/search");
private static final String REL = "search";
private final List<MethodResourceMapping> mappings;
/**
* Creates a new {@link SearchResourceMappings} from the given
*
* @param mappings
*/
public SearchResourceMappings(List<MethodResourceMapping> mappings) {
Assert.notNull(mappings, "MethodResourceMappings must not be null!");
this.mappings = mappings;
}
/**
* Returns the method mapped to the given path.
*
* @param path must not be {@literal null} or empty.
* @return
*/
public Method getMappedMethod(String path) {
Assert.hasText(path, "Path must not be null or empty!");
for (MethodResourceMapping mapping : mappings) {
if (mapping.getPath().matches(path)) {
return mapping.getMethod();
}
}
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.ResourceMapping#getPath()
*/
@Override
public Path getPath() {
return PATH;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.ResourceMapping#getRel()
*/
@Override
public String getRel() {
return REL;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.repository.mapping.ResourceMapping#isExported()
*/
@Override
public Boolean isExported() {
return !mappings.isEmpty();
}
/* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<MethodResourceMapping> iterator() {
return mappings.iterator();
}
}

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