DATAREST-93 - Further refactorings and refinements regarding the search resource mapping.
This commit is contained in:
@@ -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}.
|
||||
*
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
|
||||
|
||||
PersistentProperty persistentProperty = association.getInverse();
|
||||
|
||||
if (!metadata.isMapped(persistentProperty)) {
|
||||
if (!metadata.isExported(persistentProperty)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -66,7 +66,7 @@ public class RepositoryTestsConfig {
|
||||
|
||||
@Bean
|
||||
public UriDomainClassConverter uriDomainClassConverter() {
|
||||
return new UriDomainClassConverter();
|
||||
return new UriDomainClassConverter(repositories(), domainClassConverter());
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
Reference in New Issue
Block a user