DATAREST-467 - RepositoryEntityLinks now exposes methods to create links to search resources.

RepositoryEntityLinks now exposes methods to obtain links to all search resources and individual ones, including overloads to pre-expand Pageable and Sort parameters potentially contained in the method signature.

Search links now also contain a projection template variable if the type returned by the query method backing the search resource has projections registered.
This commit is contained in:
Oliver Gierke
2015-01-27 08:23:50 +01:00
parent 7d9db83e97
commit 14cee9c94f
15 changed files with 719 additions and 116 deletions

View File

@@ -16,6 +16,9 @@
package org.springframework.data.rest.core.mapping;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
/**
* A {@link ResourceMapping} that is backed by a {@link Method}.
@@ -44,4 +47,13 @@ public interface MethodResourceMapping extends ResourceMapping {
* @return
*/
boolean isSortableResource();
/**
* Returns the domain type that the query method returns. This will inspect wrapper types ({@link Collection}s,
* {@link Map}s, {@link Optional}s etc.) for their elemtn or value types.
*
* @return will never be {@literal null}.
* @since 2.3
*/
Class<?> getReturnedDomainType();
}

View File

@@ -25,6 +25,7 @@ import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.core.annotation.RestResource;
@@ -48,6 +49,7 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping {
private final Method method;
private final boolean paging;
private final boolean sorting;
private final RepositoryMetadata metadata;
private final List<ParameterMetadata> parameterMetadata;
@@ -57,7 +59,7 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping {
* @param method must not be {@literal null}.
* @param resourceMapping must not be {@literal null}.
*/
public RepositoryMethodResourceMapping(Method method, ResourceMapping resourceMapping) {
public RepositoryMethodResourceMapping(Method method, ResourceMapping resourceMapping, RepositoryMetadata metadata) {
Assert.notNull(method, "Method must not be null!");
Assert.notNull(resourceMapping, "ResourceMapping must not be null!");
@@ -76,6 +78,7 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping {
this.paging = parameterTypes.contains(Pageable.class);
this.sorting = parameterTypes.contains(Sort.class);
this.metadata = metadata;
}
private static final List<ParameterMetadata> discoverParameterMetadata(Method method, String baseRel) {
@@ -162,4 +165,13 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping {
public ResourceDescription getDescription() {
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.MethodResourceMapping#getProjectionSourceType()
*/
@Override
public Class<?> getReturnedDomainType() {
return metadata.getReturnedDomainClass(method);
}
}

View File

@@ -132,7 +132,7 @@ public class RepositoryResourceMappings implements ResourceMappings {
if (resourceMapping.isExported()) {
for (Method queryMethod : repositoryInformation.getQueryMethods()) {
RepositoryMethodResourceMapping methodMapping = new RepositoryMethodResourceMapping(queryMethod,
resourceMapping);
resourceMapping, repositoryInformation);
if (methodMapping.isExported()) {
mappings.add(methodMapping);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2013-2015 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.
@@ -17,9 +17,11 @@ package org.springframework.data.rest.core.mapping;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.springframework.data.rest.core.Path;
import org.springframework.util.Assert;
@@ -77,6 +79,46 @@ public class SearchResourceMappings implements Iterable<MethodResourceMapping>,
return mapping == null ? null : mapping.getMethod();
}
/**
* Returns the mappings for all exported query methods.
*
* @return
* @since 2.3
*/
public Iterable<MethodResourceMapping> getExportedMappings() {
Set<MethodResourceMapping> result = new HashSet<MethodResourceMapping>(mappings.values().size());
for (MethodResourceMapping mapping : this) {
if (mapping.isExported()) {
result.add(mapping);
}
}
return result;
}
/**
* Returns the {@link MappingResourceMetadata} for the given relation name.
*
* @param rel must not be {@literal null} or empty.
* @return
* @since 2.3
*/
public MethodResourceMapping getExportedMethodMappingForRel(String rel) {
Assert.hasText(rel, "Rel must not be null or empty!");
for (MethodResourceMapping mapping : this) {
if (mapping.isExported() && mapping.getRel().endsWith(rel)) {
return mapping;
}
}
return null;
}
/*
* (non-Javadoc)
* @see org.springframework.data.rest.core.mapping.ResourceMapping#getPath()

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2013-2014 the original author or authors.
* Copyright 2013-2015 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.
@@ -45,7 +45,7 @@ public class RepositoryMethodResourceMappingUnitTests {
public void defaultsMappingToMethodName() throws Exception {
Method method = PersonRepository.class.getMethod("findByLastname", String.class);
ResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
ResourceMapping mapping = getMappingFor(method);
assertThat(mapping.getPath(), is(new Path("findByLastname")));
}
@@ -54,7 +54,7 @@ public class RepositoryMethodResourceMappingUnitTests {
public void usesConfiguredNameWithLeadingSlash() throws Exception {
Method method = PersonRepository.class.getMethod("findByFirstname", String.class);
ResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
ResourceMapping mapping = getMappingFor(method);
assertThat(mapping.getPath(), is(new Path("bar")));
}
@@ -66,7 +66,7 @@ public class RepositoryMethodResourceMappingUnitTests {
public void doesNotDiscoverAnyParametersIfNotAnnotated() throws Exception {
Method method = PersonRepository.class.getMethod("findByLastname", String.class);
MethodResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
MethodResourceMapping mapping = getMappingFor(method);
assertThat(mapping.getParametersMetadata().getParameterNames(), is(emptyIterable()));
}
@@ -78,7 +78,7 @@ public class RepositoryMethodResourceMappingUnitTests {
public void resolvesParameterNamesIfNotAnnotated() throws Exception {
Method method = PersonRepository.class.getMethod("findByFirstname", String.class);
MethodResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
MethodResourceMapping mapping = getMappingFor(method);
assertThat(mapping.getParametersMetadata().getParameterNames(), hasSize(1));
assertThat(mapping.getParametersMetadata().getParameterNames(), hasItem("firstname"));
@@ -91,7 +91,7 @@ public class RepositoryMethodResourceMappingUnitTests {
public void considersPagingFinderAPagingResource() throws Exception {
Method method = PersonRepository.class.getMethod("findByEmailAddress", String.class, Pageable.class);
MethodResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
MethodResourceMapping mapping = getMappingFor(method);
assertThat(mapping.isPagingResource(), is(true));
}
@@ -100,7 +100,7 @@ public class RepositoryMethodResourceMappingUnitTests {
public void usesMethodNameAsRelFallbackEvenIfPathIsConfigured() throws Exception {
Method method = PersonRepository.class.getMethod("findByEmailAddress", String.class, Pageable.class);
MethodResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
MethodResourceMapping mapping = getMappingFor(method);
assertThat(mapping.getRel(), is("findByEmailAddress"));
}
@@ -112,16 +112,33 @@ public class RepositoryMethodResourceMappingUnitTests {
public void considersResourceSortableIfSortParameterIsPresent() throws Exception {
Method method = PersonRepository.class.getMethod("findByEmailAddress", String.class, Sort.class);
RepositoryMethodResourceMapping mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
RepositoryMethodResourceMapping mapping = getMappingFor(method);
assertThat(mapping.isSortableResource(), is(true));
method = PersonRepository.class.getMethod("findByEmailAddress", String.class, Pageable.class);
mapping = new RepositoryMethodResourceMapping(method, resourceMapping);
mapping = getMappingFor(method);
assertThat(mapping.isSortableResource(), is(false));
}
/**
* @see DATAREST-467
*/
@Test
@SuppressWarnings("rawtypes")
public void returnsDomainTypeAsProjectionSourceType() throws Exception {
Method method = PersonRepository.class.getMethod("findByLastname", String.class);
MethodResourceMapping mapping = getMappingFor(method);
assertThat(mapping.getReturnedDomainType(), is(equalTo((Class) Person.class)));
}
private RepositoryMethodResourceMapping getMappingFor(Method method) {
return new RepositoryMethodResourceMapping(method, resourceMapping, metadata);
}
static class Person {}
interface PersonRepository extends Repository<Person, Long> {
@@ -138,5 +155,7 @@ public class RepositoryMethodResourceMappingUnitTests {
Page<Person> findByEmailAddress(String email, Pageable pageable);
Page<Person> findByEmailAddress(String email, Sort pageable);
int countByLastname(String lastname);
}
}