DATAREST-1176 - Repository method exposure can now be controlled via RepositoryRestConfiguration.
The default exposure of repository methods is now controlled via RepositoryRestConfiguration.setExposeRepositoryMethodsByDefault(…). That allows us to remove the additional API from RepositoryDetectionStrategy as the mere detection is an orthogonal topic. Also added RepositoryRestConfiguration.disableDefaultExposure() to set the RepositoryDetectionStategy to ANNOTATED and disables default method exposure in one go. That can be exposed via a Spring Boot configuration property downstream.
This commit is contained in:
@@ -25,6 +25,8 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy;
|
||||
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.RepositoryDetectionStrategies;
|
||||
import org.springframework.data.rest.core.support.EntityLookup;
|
||||
@@ -65,6 +67,7 @@ public class RepositoryRestConfiguration {
|
||||
private ResourceMappingConfiguration domainMappings = new ResourceMappingConfiguration();
|
||||
private ResourceMappingConfiguration repoMappings = new ResourceMappingConfiguration();
|
||||
private RepositoryDetectionStrategy repositoryDetectionStrategy = RepositoryDetectionStrategies.DEFAULT;
|
||||
private boolean exposeRepositoryMethodsByDefault = true;
|
||||
|
||||
/**
|
||||
* The {@link RelProvider} to be used to calculate the link relation defaults for repositories.
|
||||
@@ -571,6 +574,47 @@ public class RepositoryRestConfiguration {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether to expose repository methods by default.
|
||||
*
|
||||
* @since 3.1
|
||||
* @see #setExposeRepositoryMethodsByDefault(boolean)
|
||||
*/
|
||||
public boolean exposeRepositoryMethodsByDefault() {
|
||||
return this.exposeRepositoryMethodsByDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to expose repository methods by default. If this is disabled, CRUD methods must be annotated with
|
||||
* {@link RestResource} explicitly to expose the default set of resources (opt-in). If this is set to {@literal true}
|
||||
* (default), repository methods methods are exposed unless explictly annotated with {@link RestResource} and
|
||||
* {@link RestResource#exported()} set to {@literal false}.
|
||||
*
|
||||
* @since 3.1
|
||||
* @see #setRepositoryDetectionStrategy(RepositoryDetectionStrategy)
|
||||
*/
|
||||
public void setExposeRepositoryMethodsByDefault(boolean exposeRepositoryMethodsByDefault) {
|
||||
this.exposeRepositoryMethodsByDefault = exposeRepositoryMethodsByDefault;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disables the default exposure of repositories entirely. I.e. repositories to be exported must now be explicitly
|
||||
* annotated with {@link RepositoryRestResource} and methods need to be annotated with {@link RestResource} to trigger
|
||||
* exposure of default resources. Basically a shortcut for calling both
|
||||
* {@link #setRepositoryDetectionStrategy(RepositoryDetectionStrategy)} to
|
||||
* {@link RepositoryDetectionStrategies#ANNOTATED} and setting {@link #setExposeRepositoryMethodsByDefault(boolean)}
|
||||
* to {@literal false}.
|
||||
*
|
||||
* @since 3.1
|
||||
* @see #setRepositoryDetectionStrategy(RepositoryDetectionStrategy)
|
||||
* @see #setExposeRepositoryMethodsByDefault(boolean)
|
||||
*/
|
||||
public void disableDefaultExposure() {
|
||||
|
||||
setRepositoryDetectionStrategy(RepositoryDetectionStrategies.ANNOTATED);
|
||||
setExposeRepositoryMethodsByDefault(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link RepositoryCorsRegistry} to configure Cross-origin resource sharing.
|
||||
*
|
||||
|
||||
@@ -19,6 +19,7 @@ import static org.springframework.data.rest.core.mapping.ResourceType.*;
|
||||
import static org.springframework.http.HttpMethod.*;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collections;
|
||||
@@ -47,15 +48,14 @@ public class CrudMethodsSupportedHttpMethods implements SupportedHttpMethods {
|
||||
* Creates a new {@link CrudMethodsSupportedHttpMethods} for the given {@link CrudMethods}.
|
||||
*
|
||||
* @param crudMethods must not be {@literal null}.
|
||||
* @param methodsExposedByDefault whether repository methods should be considered exposed by default or need to be
|
||||
* annotated with {@link RestResource} to really be visible.
|
||||
*/
|
||||
public CrudMethodsSupportedHttpMethods(CrudMethods crudMethods, RepositoryResourceMappings provider) {
|
||||
public CrudMethodsSupportedHttpMethods(CrudMethods crudMethods, boolean methodsExposedByDefault) {
|
||||
|
||||
Assert.notNull(crudMethods, "CrudMethods must not be null!");
|
||||
|
||||
boolean exportedDefault = provider.getRepositoryDetectionStrategy()
|
||||
!= RepositoryDetectionStrategy.RepositoryDetectionStrategies.EXPLICIT_METHOD_ANNOTATED;
|
||||
|
||||
this.exposedMethods = new DefaultExposureAwareCrudMethods(crudMethods, exportedDefault);
|
||||
this.exposedMethods = new DefaultExposureAwareCrudMethods(crudMethods, methodsExposedByDefault);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -141,16 +141,12 @@ public class CrudMethodsSupportedHttpMethods implements SupportedHttpMethods {
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private static class DefaultExposureAwareCrudMethods implements ExposureAwareCrudMethods {
|
||||
|
||||
private final @NonNull CrudMethods crudMethods;
|
||||
private final boolean exportedDefault;
|
||||
|
||||
DefaultExposureAwareCrudMethods(CrudMethods crudMethods, boolean exportedDefault) {
|
||||
this.crudMethods = crudMethods;
|
||||
this.exportedDefault = exportedDefault;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.mapping.ExposureAwareCrudMethods#exposesSave()
|
||||
|
||||
@@ -57,7 +57,8 @@ class RepositoryAwareResourceMetadata implements ResourceMetadata {
|
||||
this.mapping = mapping;
|
||||
this.provider = provider;
|
||||
this.repositoryMetadata = repositoryMetadata;
|
||||
this.crudMethodsSupportedHttpMethods = new CrudMethodsSupportedHttpMethods(repositoryMetadata.getCrudMethods(), provider);
|
||||
this.crudMethodsSupportedHttpMethods = new CrudMethodsSupportedHttpMethods(repositoryMetadata.getCrudMethods(),
|
||||
provider.exposeMethodsByDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,9 +61,11 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping {
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param resourceMapping must not be {@literal null}.
|
||||
* @param metadata can be {@literal null}.
|
||||
* @param whether the methods are supposed to be exported by default.
|
||||
*/
|
||||
public RepositoryMethodResourceMapping(Method method, ResourceMapping resourceMapping, RepositoryMetadata metadata,
|
||||
RepositoryDetectionStrategy strategy) {
|
||||
boolean exposeMethodsByDefault) {
|
||||
|
||||
Assert.notNull(method, "Method must not be null!");
|
||||
Assert.notNull(resourceMapping, "ResourceMapping must not be null!");
|
||||
@@ -71,7 +73,7 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping {
|
||||
RestResource annotation = AnnotationUtils.findAnnotation(method, RestResource.class);
|
||||
String resourceRel = resourceMapping.getRel();
|
||||
|
||||
this.isExported = determineIsExported(strategy, annotation);
|
||||
this.isExported = annotation != null ? annotation.exported() : exposeMethodsByDefault;
|
||||
this.rel = annotation == null || !StringUtils.hasText(annotation.rel()) ? method.getName() : annotation.rel();
|
||||
this.path = annotation == null || !StringUtils.hasText(annotation.path()) ? new Path(method.getName())
|
||||
: new Path(annotation.path());
|
||||
@@ -85,14 +87,6 @@ class RepositoryMethodResourceMapping implements MethodResourceMapping {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
private boolean determineIsExported(RepositoryDetectionStrategy strategy, RestResource annotation) {
|
||||
|
||||
boolean exportedDefault = strategy
|
||||
!= RepositoryDetectionStrategy.RepositoryDetectionStrategies.EXPLICIT_METHOD_ANNOTATED;
|
||||
|
||||
return annotation != null ? annotation.exported() : exportedDefault;
|
||||
}
|
||||
|
||||
private static final List<ParameterMetadata> discoverParameterMetadata(Method method, String baseRel) {
|
||||
|
||||
List<ParameterMetadata> result = new ArrayList<ParameterMetadata>();
|
||||
|
||||
@@ -26,9 +26,9 @@ import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.context.PersistentEntities;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.annotation.RestResource;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.hateoas.RelProvider;
|
||||
import org.springframework.hateoas.core.EvoInflectorRelProvider;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -40,46 +40,31 @@ import org.springframework.util.Assert;
|
||||
public class RepositoryResourceMappings extends PersistentEntitiesResourceMappings {
|
||||
|
||||
private final Repositories repositories;
|
||||
private final RepositoryDetectionStrategy strategy;
|
||||
private final RepositoryRestConfiguration configuration;
|
||||
private final Map<Class<?>, SearchResourceMappings> searchCache = new HashMap<Class<?>, SearchResourceMappings>();
|
||||
|
||||
/**
|
||||
* Creates a new {@link RepositoryResourceMappings} using the given {@link Repositories} and
|
||||
* {@link PersistentEntities}.
|
||||
*
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param entities must not be {@literal null}.
|
||||
* @param strategy must not be {@literal null}.
|
||||
*/
|
||||
public RepositoryResourceMappings(Repositories repositories, PersistentEntities entities,
|
||||
RepositoryDetectionStrategy strategy) {
|
||||
this(repositories, entities, strategy, new EvoInflectorRelProvider());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link RepositoryResourceMappings} from the given {@link RepositoryRestConfiguration},
|
||||
* {@link Repositories} and {@link RelProvider}.
|
||||
*
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param entities must not be {@literal null}.
|
||||
* @param strategy must not be {@literal null}.
|
||||
* @param relProvider must not be {@literal null}.
|
||||
* @param configuration must not be {@literal null}.
|
||||
*/
|
||||
public RepositoryResourceMappings(Repositories repositories, PersistentEntities entities, RepositoryDetectionStrategy strategy,
|
||||
RelProvider relProvider) {
|
||||
public RepositoryResourceMappings(Repositories repositories, PersistentEntities entities,
|
||||
RepositoryRestConfiguration configuration) {
|
||||
|
||||
super(entities);
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(strategy, "RepositoryDetectionStrategy must not be null!");
|
||||
Assert.notNull(configuration, "RepositoryRestConfiguration must not be null!");
|
||||
|
||||
this.repositories = repositories;
|
||||
this.strategy = strategy;
|
||||
this.populateCache(repositories, relProvider, strategy);
|
||||
this.configuration = configuration;
|
||||
this.populateCache(repositories, configuration);
|
||||
}
|
||||
|
||||
private final void populateCache(Repositories repositories, RelProvider provider,
|
||||
RepositoryDetectionStrategy strategy) {
|
||||
private final void populateCache(Repositories repositories, RepositoryRestConfiguration configuration) {
|
||||
|
||||
for (Class<?> type : repositories) {
|
||||
|
||||
@@ -87,6 +72,9 @@ public class RepositoryResourceMappings extends PersistentEntitiesResourceMappin
|
||||
Class<?> repositoryInterface = repositoryInformation.getRepositoryInterface();
|
||||
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(type);
|
||||
|
||||
RepositoryDetectionStrategy strategy = configuration.getRepositoryDetectionStrategy();
|
||||
RelProvider provider = configuration.getRelProvider();
|
||||
|
||||
CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(repositoryInformation, strategy,
|
||||
provider);
|
||||
RepositoryAwareResourceMetadata information = new RepositoryAwareResourceMetadata(entity, mapping, this,
|
||||
@@ -120,7 +108,7 @@ public class RepositoryResourceMappings extends PersistentEntitiesResourceMappin
|
||||
if (resourceMapping.isExported()) {
|
||||
for (Method queryMethod : repositoryInformation.getQueryMethods()) {
|
||||
RepositoryMethodResourceMapping methodMapping = new RepositoryMethodResourceMapping(queryMethod,
|
||||
resourceMapping, repositoryInformation, strategy);
|
||||
resourceMapping, repositoryInformation, exposeMethodsByDefault());
|
||||
if (methodMapping.isExported()) {
|
||||
mappings.add(methodMapping);
|
||||
}
|
||||
@@ -159,7 +147,14 @@ public class RepositoryResourceMappings extends PersistentEntitiesResourceMappin
|
||||
return repositories.hasRepositoryFor(property.getActualType()) && super.isMapped(property);
|
||||
}
|
||||
|
||||
public RepositoryDetectionStrategy getRepositoryDetectionStrategy() {
|
||||
return strategy;
|
||||
/**
|
||||
* Returns whether to expose repository methods by default, i.e. without the need to explicitly annotate them with
|
||||
* {@link RestResource}.
|
||||
*
|
||||
* @since 3.1
|
||||
* @see RepositoryRestConfiguration#exposeRepositoryMethodsByDefault()
|
||||
*/
|
||||
public boolean exposeMethodsByDefault() {
|
||||
return configuration.exposeRepositoryMethodsByDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,7 @@ import org.springframework.http.HttpMethod;
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class CrudMethodsSupportedHttpMethodsUnitTests {
|
||||
|
||||
@Mock
|
||||
private RepositoryResourceMappings mappings;
|
||||
@Mock private RepositoryResourceMappings mappings;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
@@ -134,7 +133,7 @@ public class CrudMethodsSupportedHttpMethodsUnitTests {
|
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface);
|
||||
CrudMethods crudMethods = new DefaultCrudMethods(metadata);
|
||||
|
||||
return new CrudMethodsSupportedHttpMethods(crudMethods, mappings);
|
||||
return new CrudMethodsSupportedHttpMethods(crudMethods, mappings.exposeMethodsByDefault());
|
||||
}
|
||||
|
||||
private static void assertMethodsSupported(SupportedHttpMethods methods, ResourceType type, boolean supported,
|
||||
|
||||
@@ -131,8 +131,7 @@ public class RepositoryMethodResourceMappingUnitTests {
|
||||
}
|
||||
|
||||
private RepositoryMethodResourceMapping getMappingFor(Method method) {
|
||||
RepositoryDetectionStrategy strategy = RepositoryDetectionStrategies.DEFAULT;
|
||||
return new RepositoryMethodResourceMapping(method, resourceMapping, metadata, strategy);
|
||||
return new RepositoryMethodResourceMapping(method, resourceMapping, metadata, true);
|
||||
}
|
||||
|
||||
static class Person {}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.rest.core.mapping;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -32,13 +33,15 @@ import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.context.PersistentEntities;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.Path;
|
||||
import org.springframework.data.rest.core.config.EnumTranslationConfiguration;
|
||||
import org.springframework.data.rest.core.config.MetadataConfiguration;
|
||||
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.domain.Author;
|
||||
import org.springframework.data.rest.core.domain.CreditCard;
|
||||
import org.springframework.data.rest.core.domain.JpaRepositoryConfig;
|
||||
import org.springframework.data.rest.core.domain.Person;
|
||||
import org.springframework.data.rest.core.domain.Profile;
|
||||
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.RepositoryDetectionStrategies;
|
||||
import org.springframework.hateoas.core.EvoInflectorRelProvider;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
|
||||
@@ -62,9 +65,12 @@ public class RepositoryResourceMappingsIntegrationTests {
|
||||
|
||||
mappingContext.getPersistentEntity(Profile.class);
|
||||
|
||||
RepositoryRestConfiguration configuration = new RepositoryRestConfiguration(new ProjectionDefinitionConfiguration(),
|
||||
new MetadataConfiguration(), mock(EnumTranslationConfiguration.class));
|
||||
|
||||
Repositories repositories = new Repositories(factory);
|
||||
this.mappings = new RepositoryResourceMappings(repositories, new PersistentEntities(Arrays.asList(mappingContext)),
|
||||
RepositoryDetectionStrategies.DEFAULT, new EvoInflectorRelProvider());
|
||||
configuration);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -107,7 +107,7 @@ public class RepositoryTestsConfig {
|
||||
public Module persistentEntityModule() {
|
||||
|
||||
RepositoryResourceMappings mappings = new RepositoryResourceMappings(repositories(), persistentEntities(),
|
||||
config().getRepositoryDetectionStrategy());
|
||||
config());
|
||||
EntityLinks entityLinks = new RepositoryEntityLinks(repositories(), mappings, config(),
|
||||
mock(PagingAndSortingTemplateVariables.class),
|
||||
Java8PluginRegistry.of(Arrays.asList(DefaultIdConverter.INSTANCE)));
|
||||
|
||||
@@ -115,7 +115,7 @@ public class RepositoryTestsConfig {
|
||||
public Module persistentEntityModule() {
|
||||
|
||||
RepositoryResourceMappings mappings = new RepositoryResourceMappings(repositories(), persistentEntities(),
|
||||
config().getRepositoryDetectionStrategy());
|
||||
config());
|
||||
EntityLinks entityLinks = new RepositoryEntityLinks(repositories(), mappings, config(),
|
||||
mock(PagingAndSortingTemplateVariables.class),
|
||||
Java8PluginRegistry.of(Arrays.asList(DefaultIdConverter.INSTANCE)));
|
||||
|
||||
@@ -639,8 +639,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
|
||||
@Bean
|
||||
public RepositoryResourceMappings resourceMappings() {
|
||||
return new RepositoryResourceMappings(repositories(), persistentEntities(),
|
||||
repositoryRestConfiguration().getRepositoryDetectionStrategy(), repositoryRestConfiguration().getRelProvider());
|
||||
return new RepositoryResourceMappings(repositories(), persistentEntities(), repositoryRestConfiguration());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user