diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java index 44415d095..943f99eb0 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/RepositoryRestConfiguration.java @@ -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. * diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethods.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethods.java index de52007be..45ff1196a 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethods.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethods.java @@ -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() diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryAwareResourceMetadata.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryAwareResourceMetadata.java index 4c6dbd966..a852382ed 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryAwareResourceMetadata.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryAwareResourceMetadata.java @@ -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()); } /** diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMapping.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMapping.java index feafcc07e..9b30dfac4 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMapping.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMapping.java @@ -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 discoverParameterMetadata(Method method, String baseRel) { List result = new ArrayList(); diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryResourceMappings.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryResourceMappings.java index a5a7f8e71..159c895ec 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryResourceMappings.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryResourceMappings.java @@ -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, SearchResourceMappings> searchCache = new HashMap, 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(); } } diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethodsUnitTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethodsUnitTests.java index b7db49bc9..ff8cade0a 100755 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethodsUnitTests.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/CrudMethodsSupportedHttpMethodsUnitTests.java @@ -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, diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMappingUnitTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMappingUnitTests.java index f6ab6ed8b..33ab554c5 100755 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMappingUnitTests.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryMethodResourceMappingUnitTests.java @@ -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 {} diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryResourceMappingsIntegrationTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryResourceMappingsIntegrationTests.java index 779f6d312..32e7838d7 100755 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryResourceMappingsIntegrationTests.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryResourceMappingsIntegrationTests.java @@ -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 diff --git a/spring-data-rest-tests/spring-data-rest-tests-core/src/test/java/org/springframework/data/rest/tests/RepositoryTestsConfig.java b/spring-data-rest-tests/spring-data-rest-tests-core/src/test/java/org/springframework/data/rest/tests/RepositoryTestsConfig.java index 5586e1f5a..9900c4125 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-core/src/test/java/org/springframework/data/rest/tests/RepositoryTestsConfig.java +++ b/spring-data-rest-tests/spring-data-rest-tests-core/src/test/java/org/springframework/data/rest/tests/RepositoryTestsConfig.java @@ -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))); diff --git a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java index 2f8a13e1f..4d6536276 100644 --- a/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java +++ b/spring-data-rest-tests/spring-data-rest-tests-jpa/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java @@ -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))); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java index ba845b8e6..bf8cd3b35 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java @@ -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()); } /**