From a10aa934b794bffb7bfbbac22e7bfc72ea4fdcc8 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Tue, 16 Jan 2018 17:33:34 +0100 Subject: [PATCH] DATAREST-1176 - Repository method exposure can now be controlled via RepositoryRestConfiguration. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../config/RepositoryRestConfiguration.java | 44 +++++++++++++++++++ .../mapping/RepositoryDetectionStrategy.java | 35 --------------- .../mapping/RepositoryResourceMappings.java | 34 +++++--------- ...epositoryDetectionStrategiesUnitTests.java | 15 ------- ...ositoryMethodResourceMappingUnitTests.java | 6 +-- ...itoryResourceMappingsIntegrationTests.java | 12 +++-- .../rest/tests/RepositoryTestsConfig.java | 2 +- .../webmvc/json/RepositoryTestsConfig.java | 2 +- .../RepositoryRestMvcConfiguration.java | 3 +- 9 files changed, 70 insertions(+), 83 deletions(-) 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 52dfa568b..4840e97d3 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/RepositoryDetectionStrategy.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryDetectionStrategy.java index 049b735f3..d9b14ecf7 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryDetectionStrategy.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryDetectionStrategy.java @@ -40,17 +40,6 @@ public interface RepositoryDetectionStrategy { */ boolean isExported(RepositoryMetadata metadata); - /** - * Returns whether to expose repository methods by default, i.e. without the need to explicitly annotate them with - * {@link RestResource}. - * - * @return - * @since 3.1 - */ - default boolean exposeMethodsByDefault() { - return true; - } - /** * A variety of strategies to determine repository exposure. * @@ -108,30 +97,6 @@ public interface RepositoryDetectionStrategy { public boolean isExported(RepositoryMetadata metadata) { return isExplicitlyExported(metadata.getRepositoryInterface(), false); } - }, - - /** - * Behaves like the {@link RepositoryDetectionStrategies#ANNOTATED} strategy on repository level, but only exports - * the methods of the repository that have been explicitly annotated with {@link RestResource}. CRUD methods need to - * be annotated, too, for the default collection resource exposure to be applied. - * - * @since 3.1 - */ - EXPLICITLY_ANNOTATED { - - @Override - public boolean isExported(RepositoryMetadata metadata) { - return isExplicitlyExported(metadata.getRepositoryInterface(), false); - } - - /* - * (non-Javadoc) - * @see org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy#exposeMethodsByDefault() - */ - @Override - public boolean exposeMethodsByDefault() { - return false; - } }; /** 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 6f2ce6499..d9b004642 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 @@ -29,7 +29,6 @@ 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; /** @@ -41,22 +40,9 @@ 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}. @@ -67,20 +53,19 @@ public class RepositoryResourceMappings extends PersistentEntitiesResourceMappin * @param relProvider must not be {@literal null}. */ public RepositoryResourceMappings(Repositories repositories, PersistentEntities entities, - RepositoryDetectionStrategy strategy, RelProvider relProvider) { + 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) { @@ -88,6 +73,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, @@ -165,9 +153,9 @@ public class RepositoryResourceMappings extends PersistentEntitiesResourceMappin * {@link RestResource}. * * @since 3.1 - * @see RepositoryDetectionStrategy#exposeMethodsByDefault() + * @see RepositoryRestConfiguration#exposeRepositoryMethodsByDefault() */ public boolean exposeMethodsByDefault() { - return strategy.exposeMethodsByDefault(); + return configuration.exposeRepositoryMethodsByDefault(); } } diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryDetectionStrategiesUnitTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryDetectionStrategiesUnitTests.java index c5c285438..7c656c018 100755 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryDetectionStrategiesUnitTests.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryDetectionStrategiesUnitTests.java @@ -89,21 +89,6 @@ public class RepositoryDetectionStrategiesUnitTests { }); } - @Test // DATAREST-1176 - public void onlyExplicitAnnotatedMethodsAreExposed() { - - assertExposures(EXPLICITLY_ANNOTATED, new HashMap, Boolean>() { - { - put(AnnotatedRepository.class, true); - put(HiddenRepository.class, false); - put(PublicRepository.class, false); - put(PackageProtectedRepository.class, false); - } - }); - - assertThat(EXPLICITLY_ANNOTATED.exposeMethodsByDefault()).isFalse(); - } - private static void assertExposures(RepositoryDetectionStrategy strategy, Map, Boolean> expected) { for (Entry, Boolean> entry : expected.entrySet()) { 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 d4cf1cc0e..061e85324 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 @@ -38,9 +38,9 @@ import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.Re */ public class RepositoryMethodResourceMappingUnitTests { - RepositoryDetectionStrategy strategy = RepositoryDetectionStrategies.DEFAULT; RepositoryMetadata metadata = new DefaultRepositoryMetadata(PersonRepository.class); - RepositoryCollectionResourceMapping resourceMapping = new RepositoryCollectionResourceMapping(metadata, strategy); + RepositoryCollectionResourceMapping resourceMapping = new RepositoryCollectionResourceMapping(metadata, + RepositoryDetectionStrategies.DEFAULT); @Test public void defaultsMappingToMethodName() throws Exception { @@ -131,7 +131,7 @@ public class RepositoryMethodResourceMappingUnitTests { } private RepositoryMethodResourceMapping getMappingFor(Method method) { - return new RepositoryMethodResourceMapping(method, resourceMapping, metadata, strategy.exposeMethodsByDefault()); + 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 97d9e3dc3..4a0bc3214 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 65333eee5..b1f8cc49f 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 8d7d2475b..db4635266 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 cebef5fb5..fe4c5dc0a 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()); } /**