diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/ProjectionDefinitionConfiguration.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/ProjectionDefinitionConfiguration.java index 059ab56d0..a7474d0dc 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/ProjectionDefinitionConfiguration.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/config/ProjectionDefinitionConfiguration.java @@ -15,13 +15,11 @@ */ package org.springframework.data.rest.core.config; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.data.rest.core.mapping.ResourceMetadata; import org.springframework.data.rest.core.projection.ProjectionDefinitions; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; @@ -44,28 +42,7 @@ public class ProjectionDefinitionConfiguration implements ProjectionDefinitions * Creates a new {@link ProjectionDefinitionConfiguration}. */ public ProjectionDefinitionConfiguration() { - this(Collections. emptySet()); - } - - /** - * Creates a new {@link ProjectionDefinitionConfiguration} from the given {@link ResourceMetadata} instances. - * - * @param resourceMetadata must not be {@literal null}. - */ - public ProjectionDefinitionConfiguration(Iterable resourceMetadata) { - - Assert.notNull(resourceMetadata, "ResourceMetadata must not be null!"); - this.projectionDefinitions = new HashMap>(); - - for (ResourceMetadata metadata : resourceMetadata) { - - Class projection = metadata.getExcerptProjection(); - - if (projection != null) { - addProjection(projection); - } - } } /* @@ -106,8 +83,8 @@ public class ProjectionDefinitionConfiguration implements ProjectionDefinitions String name = annotation.name(); Class[] sourceTypes = annotation.types(); - return StringUtils.hasText(name) ? addProjection(projectionType, name, sourceTypes) : addProjection(projectionType, - sourceTypes); + return StringUtils.hasText(name) ? addProjection(projectionType, name, sourceTypes) + : addProjection(projectionType, sourceTypes); } /** @@ -132,7 +109,8 @@ public class ProjectionDefinitionConfiguration implements ProjectionDefinitions * @param sourceTypes must not be {@literal null} or empty. * @return */ - public ProjectionDefinitionConfiguration addProjection(Class projectionType, String name, Class... sourceTypes) { + public ProjectionDefinitionConfiguration addProjection(Class projectionType, String name, + Class... sourceTypes) { Assert.notNull(projectionType, "Projection type must not be null!"); Assert.hasText(name, "Name must not be null or empty!"); 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 e892eb35c..d37b19fd5 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 @@ -20,6 +20,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy; +import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.RepositoryDetectionStrategies; import org.springframework.hateoas.MediaTypes; import org.springframework.http.MediaType; import org.springframework.util.Assert; @@ -52,10 +54,12 @@ public class RepositoryRestConfiguration { private List> exposeIdsFor = new ArrayList>(); private ResourceMappingConfiguration domainMappings = new ResourceMappingConfiguration(); private ResourceMappingConfiguration repoMappings = new ResourceMappingConfiguration(); + private RepositoryDetectionStrategy repositoryDetectionStrategy = RepositoryDetectionStrategies.DEFAULT; + private final ProjectionDefinitionConfiguration projectionConfiguration; private final MetadataConfiguration metadataConfiguration; - private final EnumTranslationConfiguration enumSerializationConfiguration; + private final EnumTranslationConfiguration enumTranslationConfiguration; private boolean enableEnumTranslation = false; /** @@ -63,17 +67,18 @@ public class RepositoryRestConfiguration { * * @param projectionConfiguration must not be {@literal null}. * @param metadataConfiguration must not be {@literal null}. + * @param enumTranslationConfiguration must not be {@literal null}. */ public RepositoryRestConfiguration(ProjectionDefinitionConfiguration projectionConfiguration, MetadataConfiguration metadataConfiguration, EnumTranslationConfiguration enumTranslationConfiguration) { Assert.notNull(projectionConfiguration, "ProjectionDefinitionConfiguration must not be null!"); Assert.notNull(metadataConfiguration, "MetadataConfiguration must not be null!"); - Assert.notNull(enumTranslationConfiguration, " must not be null!"); + Assert.notNull(enumTranslationConfiguration, "EnumTranslationConfiguration must not be null!"); this.projectionConfiguration = projectionConfiguration; this.metadataConfiguration = metadataConfiguration; - this.enumSerializationConfiguration = enumTranslationConfiguration; + this.enumTranslationConfiguration = enumTranslationConfiguration; } /** @@ -489,7 +494,7 @@ public class RepositoryRestConfiguration { * details see {@link EnumTranslator}. * * @param enableEnumTranslation - * @see #getEnumSerializationConfiguration() + * @see #getEnumTranslationConfiguration() */ public void setEnableEnumTranslation(boolean enableEnumTranslation) { this.enableEnumTranslation = enableEnumTranslation; @@ -499,17 +504,43 @@ public class RepositoryRestConfiguration { * Returns whether enum value translation is enabled. * * @return + * @since 2.4 */ public boolean isEnableEnumTranslation() { return this.enableEnumTranslation; } /** - * Returns the {@link EnumTranslator} for + * Returns the {@link EnumTranslationConfiguration} to be used. * - * @return + * @return must not be {@literal null}. + * @since 2.4 */ - public EnumTranslationConfiguration getEnumSerializationConfiguration() { - return this.enumSerializationConfiguration; + public EnumTranslationConfiguration getEnumTranslationConfiguration() { + return this.enumTranslationConfiguration; + } + + /** + * Returns the {@link RepositoryDetectionStrategy} to be used to decide which repositories get exposed. Will be + * {@link RepositoryDetectionStrategies#DEFAULT} by default. + * + * @return will never be {@literal null}. + * @see RepositoryDetectionStrategies + * @since 2.5 + */ + public RepositoryDetectionStrategy getRepositoryDetectionStrategy() { + return repositoryDetectionStrategy; + } + + /** + * Configures the {@link RepositoryDetectionStrategy} to be used to determine which repositories get exposed. Defaults + * to {@link RepositoryDetectionStrategies#DEFAULT}. + * + * @param repositoryDetectionStrategy can be {@literal null}. + * @since 2.5 + */ + public void setRepositoryDetectionStrategy(RepositoryDetectionStrategy repositoryDetectionStrategy) { + this.repositoryDetectionStrategy = repositoryDetectionStrategy == null ? RepositoryDetectionStrategies.DEFAULT + : repositoryDetectionStrategy; } } diff --git a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMapping.java b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMapping.java index 6657d72c5..f1e6a123f 100644 --- a/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMapping.java +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMapping.java @@ -15,8 +15,6 @@ */ package org.springframework.data.rest.core.mapping; -import java.lang.reflect.Modifier; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.AnnotationUtils; @@ -45,11 +43,11 @@ class RepositoryCollectionResourceMapping implements CollectionResourceMapping { private final RestResource annotation; private final RepositoryRestResource repositoryAnnotation; private final CollectionResourceMapping domainTypeMapping; - private final boolean repositoryIsExportCandidate; + private final boolean repositoryExported; private final RepositoryMetadata metadata; - public RepositoryCollectionResourceMapping(RepositoryMetadata metadata) { - this(metadata, new EvoInflectorRelProvider()); + public RepositoryCollectionResourceMapping(RepositoryMetadata metadata, RepositoryDetectionStrategy strategy) { + this(metadata, new EvoInflectorRelProvider(), strategy); } /** @@ -58,22 +56,26 @@ class RepositoryCollectionResourceMapping implements CollectionResourceMapping { * * @param repositoryType must not be {@literal null}. * @param relProvider must not be {@literal null}. + * @param strategy must not be {@literal null}. */ - public RepositoryCollectionResourceMapping(RepositoryMetadata metadata, RelProvider relProvider) { + RepositoryCollectionResourceMapping(RepositoryMetadata metadata, RelProvider relProvider, + RepositoryDetectionStrategy strategy) { Assert.notNull(metadata, "Repository metadata must not be null!"); Assert.notNull(relProvider, "RelProvider must not be null!"); + Assert.notNull(strategy, "RepositoryDetectionStrategy must not be null!"); Class repositoryType = metadata.getRepositoryInterface(); this.metadata = metadata; this.annotation = AnnotationUtils.findAnnotation(repositoryType, RestResource.class); this.repositoryAnnotation = AnnotationUtils.findAnnotation(repositoryType, RepositoryRestResource.class); - this.repositoryIsExportCandidate = Modifier.isPublic(repositoryType.getModifiers()); + this.repositoryExported = strategy.isExported(metadata); Class domainType = metadata.getDomainType(); - this.domainTypeMapping = EVO_INFLECTOR_IS_PRESENT ? new EvoInflectorTypeBasedCollectionResourceMapping(domainType, - relProvider) : new TypeBasedCollectionResourceMapping(domainType, relProvider); + this.domainTypeMapping = EVO_INFLECTOR_IS_PRESENT + ? new EvoInflectorTypeBasedCollectionResourceMapping(domainType, relProvider) + : new TypeBasedCollectionResourceMapping(domainType, relProvider); if (annotation != null) { LOGGER.warn( @@ -149,16 +151,7 @@ class RepositoryCollectionResourceMapping implements CollectionResourceMapping { */ @Override public boolean isExported() { - - if (repositoryAnnotation != null) { - return repositoryAnnotation.exported(); - } - - if (annotation != null) { - return annotation.exported(); - } - - return repositoryIsExportCandidate && domainTypeMapping.isExported(); + return repositoryExported; } /* 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 new file mode 100644 index 000000000..9868373ad --- /dev/null +++ b/spring-data-rest-core/src/main/java/org/springframework/data/rest/core/mapping/RepositoryDetectionStrategy.java @@ -0,0 +1,126 @@ +/* + * Copyright 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. + * 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.core.mapping; + +import java.lang.reflect.Modifier; + +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.data.repository.core.RepositoryMetadata; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.data.rest.core.annotation.RestResource; + +/** + * The strategy to determine whether a given repository is to be exported by Spring Data REST. + * + * @author Oliver Gierke + * @since 2.5 + * @soundtrack Katinka - Ausverkauf + */ +public interface RepositoryDetectionStrategy { + + /** + * Returns whether the repository described by the given {@link RepositoryMetadata} is exported or not. + * + * @param metadata must not be {@literal null}. + * @return + */ + boolean isExported(RepositoryMetadata metadata); + + /** + * A variety of strategies to determine repository exposure. + * + * @author Oliver Gierke + * @since 2.5 + * @soundtrack Katinka - Ausverkauf + */ + public static enum RepositoryDetectionStrategies implements RepositoryDetectionStrategy { + + /** + * Considers all repositories. + */ + ALL { + + @Override + public boolean isExported(RepositoryMetadata metadata) { + return true; + } + }, + + /** + * Exposes public interfaces or ones explicitly annotated with {@link RepositoryRestResource}. + * + * @see #VISIBILITY + * @see #ANNOTATED + */ + DEFAULT { + + @Override + public boolean isExported(RepositoryMetadata metadata) { + + return isExplicitlyExported(metadata.getRepositoryInterface(), + isExplicitlyExported(metadata.getDomainType(), VISIBILITY.isExported(metadata))); + } + }, + + /** + * Considers the repository interface's visibility, which means only public interfaces will be exposed. + */ + VISIBILITY { + + @Override + public boolean isExported(RepositoryMetadata metadata) { + return Modifier.isPublic(metadata.getRepositoryInterface().getModifiers()); + }; + }, + + /** + * Considers repositories that are annotated with {@link RepositoryRestResource} or {@link RestResource} and don't + * have the {@code exported} flag not set to {@literal false}. + */ + ANNOTATED { + + @Override + public boolean isExported(RepositoryMetadata metadata) { + return isExplicitlyExported(metadata.getRepositoryInterface(), false); + } + }; + + /** + * Returns whether the given type was explicitly exported using {@link RepositoryRestResource} or + * {@link RestResource}. In case no decision can be made based on the annotations, the fallback will be used. + * + * @param type must not be {@literal null}. + * @param fallback + * @return + */ + private static boolean isExplicitlyExported(Class type, boolean fallback) { + + RepositoryRestResource restResource = AnnotationUtils.findAnnotation(type, RepositoryRestResource.class); + + if (restResource != null) { + return restResource.exported(); + } + + RestResource resource = AnnotationUtils.findAnnotation(type, RestResource.class); + + if (resource != null) { + return resource.exported(); + } + + return fallback; + } + } +} 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 5dfaa81fc..dfd536804 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 @@ -48,9 +48,11 @@ public class RepositoryResourceMappings extends PersistentEntitiesResourceMappin * * @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) { - this(repositories, entities, new EvoInflectorRelProvider()); + public RepositoryResourceMappings(Repositories repositories, PersistentEntities entities, + RepositoryDetectionStrategy strategy) { + this(repositories, entities, new EvoInflectorRelProvider(), strategy); } /** @@ -60,18 +62,22 @@ public class RepositoryResourceMappings extends PersistentEntitiesResourceMappin * @param repositories must not be {@literal null}. * @param entities must not be {@literal null}. * @param relProvider must not be {@literal null}. + * @param strategy must not be {@literal null}. */ - public RepositoryResourceMappings(Repositories repositories, PersistentEntities entities, RelProvider relProvider) { + RepositoryResourceMappings(Repositories repositories, PersistentEntities entities, RelProvider relProvider, + RepositoryDetectionStrategy strategy) { super(entities); Assert.notNull(repositories, "Repositories must not be null!"); + Assert.notNull(strategy, "RepositoryDetectionStrategy must not be null!"); this.repositories = repositories; - this.populateCache(repositories, relProvider); + this.populateCache(repositories, relProvider, strategy); } - private final void populateCache(Repositories repositories, RelProvider provider) { + private final void populateCache(Repositories repositories, RelProvider provider, + RepositoryDetectionStrategy strategy) { for (Class type : repositories) { @@ -79,7 +85,8 @@ public class RepositoryResourceMappings extends PersistentEntitiesResourceMappin Class repositoryInterface = repositoryInformation.getRepositoryInterface(); PersistentEntity entity = repositories.getPersistentEntity(type); - CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(repositoryInformation, provider); + CollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(repositoryInformation, provider, + strategy); RepositoryAwareResourceMetadata information = new RepositoryAwareResourceMetadata(entity, mapping, this, repositoryInformation); diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/config/ProjectionDefinitionConfigurationUnitTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/config/ProjectionDefinitionConfigurationUnitTests.java index 56784fbd8..8c987374d 100644 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/config/ProjectionDefinitionConfigurationUnitTests.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/config/ProjectionDefinitionConfigurationUnitTests.java @@ -17,14 +17,9 @@ package org.springframework.data.rest.core.config; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; -import static org.mockito.Mockito.*; -import java.util.Arrays; - -import org.hamcrest.Matchers; import org.junit.Test; import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration.ProjectionDefinitionKey; -import org.springframework.data.rest.core.mapping.ResourceMetadata; /** * Unit tests for {@link ProjectionDefinitionConfiguration}. @@ -155,19 +150,6 @@ public class ProjectionDefinitionConfigurationUnitTests { is(typeCompatibleWith(ParentProjection.class))); } - /** - * @see DATAREST-577 - */ - @Test - public void registersExcerptProjectionsByDefault() { - - ResourceMetadata metadata = mock(ResourceMetadata.class); - doReturn(SampleProjection.class).when(metadata).getExcerptProjection(); - - assertThat(new ProjectionDefinitionConfiguration(Arrays.asList(metadata)).getProjectionsFor(Integer.class), - Matchers.> hasEntry("name", SampleProjection.class)); - } - @Projection(name = "name", types = Integer.class) interface SampleProjection {} diff --git a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMappingUnitTests.java b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMappingUnitTests.java index 0f21071aa..d3d258b00 100644 --- a/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMappingUnitTests.java +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryCollectionResourceMappingUnitTests.java @@ -27,6 +27,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat import org.springframework.data.rest.core.Path; import org.springframework.data.rest.core.annotation.RepositoryRestResource; import org.springframework.data.rest.core.annotation.RestResource; +import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.RepositoryDetectionStrategies; /** * Unit tests for {@link RepositoryCollectionResourceMapping}. @@ -105,7 +106,8 @@ public class RepositoryCollectionResourceMappingUnitTests { } }; - RepositoryCollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(metadata); + RepositoryCollectionResourceMapping mapping = new RepositoryCollectionResourceMapping(metadata, + RepositoryDetectionStrategies.DEFAULT); assertThat(mapping.getPath(), is(new Path("/objects"))); } @@ -113,7 +115,7 @@ public class RepositoryCollectionResourceMappingUnitTests { private static CollectionResourceMapping getResourceMappingFor(Class repositoryInterface) { RepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface); - return new RepositoryCollectionResourceMapping(metadata); + return new RepositoryCollectionResourceMapping(metadata, RepositoryDetectionStrategies.DEFAULT); } public static class Person {} 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 new file mode 100644 index 000000000..0f115db4c --- /dev/null +++ b/spring-data-rest-core/src/test/java/org/springframework/data/rest/core/mapping/RepositoryDetectionStrategiesUnitTests.java @@ -0,0 +1,122 @@ +/* + * Copyright 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. + * 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.core.mapping; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; +import static org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.RepositoryDetectionStrategies.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.junit.Test; +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.core.support.DefaultRepositoryMetadata; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; +import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.RepositoryDetectionStrategies; + +/** + * Unit tests for {@link RepositoryDetectionStrategies}. + * + * @author Oliver Gierke + * @soundtrack Katinka - Ausverkauf + */ +@SuppressWarnings("serial") +public class RepositoryDetectionStrategiesUnitTests { + + /** + * @see DATAREST-473 + */ + @Test + public void allExposesAllRepositories() { + + assertExposures(ALL, new HashMap, Boolean>() { + { + put(AnnotatedRepository.class, true); + put(HiddenRepository.class, true); + put(PublicRepository.class, true); + put(PackageProtectedRepository.class, true); + } + }); + } + + /** + * @see DATAREST-473 + */ + @Test + public void defaultHonorsVisibilityAndAnnotations() { + + assertExposures(DEFAULT, new HashMap, Boolean>() { + { + put(AnnotatedRepository.class, true); + put(HiddenRepository.class, false); + put(PublicRepository.class, true); + put(PackageProtectedRepository.class, false); + } + }); + } + + /** + * @see DATAREST-473 + */ + @Test + public void visibilityHonorsTypeVisibilityOnly() { + + assertExposures(VISIBILITY, new HashMap, Boolean>() { + { + put(AnnotatedRepository.class, false); + put(HiddenRepository.class, true); + put(PublicRepository.class, true); + put(PackageProtectedRepository.class, false); + } + }); + } + + /** + * @see DATAREST-473 + */ + + @Test + public void annotatedHonorsAnnotationsOnly() { + + assertExposures(ANNOTATED, new HashMap, Boolean>() { + { + put(AnnotatedRepository.class, true); + put(HiddenRepository.class, false); + put(PublicRepository.class, false); + put(PackageProtectedRepository.class, false); + } + }); + } + + private static void assertExposures(RepositoryDetectionStrategy strategy, Map, Boolean> expected) { + + for (Entry, Boolean> entry : expected.entrySet()) { + assertThat(strategy.isExported(new DefaultRepositoryMetadata(entry.getKey())), is(entry.getValue())); + } + } + + interface PackageProtectedRepository extends Repository {} + + public interface PublicRepository extends Repository {} + + @RepositoryRestResource + interface AnnotatedRepository extends Repository {} + + @RepositoryRestResource(exported = false) + public interface HiddenRepository extends Repository {} +} 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 02de91e20..9825d1928 100644 --- 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 @@ -30,6 +30,7 @@ import org.springframework.data.repository.core.support.DefaultRepositoryMetadat import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.Path; import org.springframework.data.rest.core.annotation.RestResource; +import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.RepositoryDetectionStrategies; /** * Unit tests for {@link RepositoryMethodResourceMapping}. @@ -39,7 +40,8 @@ import org.springframework.data.rest.core.annotation.RestResource; public class RepositoryMethodResourceMappingUnitTests { RepositoryMetadata metadata = new DefaultRepositoryMetadata(PersonRepository.class); - RepositoryCollectionResourceMapping resourceMapping = new RepositoryCollectionResourceMapping(metadata); + RepositoryCollectionResourceMapping resourceMapping = new RepositoryCollectionResourceMapping(metadata, + RepositoryDetectionStrategies.DEFAULT); @Test public void defaultsMappingToMethodName() throws Exception { 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 cbb6f3474..380b07ce6 100644 --- 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 @@ -38,6 +38,8 @@ import org.springframework.data.rest.core.domain.jpa.Author; import org.springframework.data.rest.core.domain.jpa.CreditCard; import org.springframework.data.rest.core.domain.jpa.JpaRepositoryConfig; import org.springframework.data.rest.core.domain.jpa.Person; +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; import org.springframework.transaction.annotation.Transactional; @@ -62,7 +64,8 @@ public class RepositoryResourceMappingsIntegrationTests { public void setUp() { Repositories repositories = new Repositories(factory); - this.mappings = new RepositoryResourceMappings(repositories, new PersistentEntities(Arrays.asList(mappingContext))); + this.mappings = new RepositoryResourceMappings(repositories, new PersistentEntities(Arrays.asList(mappingContext)), + new EvoInflectorRelProvider(), RepositoryDetectionStrategies.DEFAULT); } @Test diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/ProjectionDefinitionRegistar.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/ProjectionDefinitionRegistar.java new file mode 100644 index 000000000..0ccf8ab7f --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/ProjectionDefinitionRegistar.java @@ -0,0 +1,78 @@ +/* + * Copyright 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. + * 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.webmvc.config; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.ObjectFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter; +import org.springframework.data.rest.core.config.RepositoryRestConfiguration; +import org.springframework.data.rest.core.mapping.RepositoryResourceMappings; +import org.springframework.data.rest.core.mapping.ResourceMappings; +import org.springframework.data.rest.core.mapping.ResourceMetadata; +import org.springframework.util.Assert; + +/** + * {@link BeanPostProcessor} to make sure all excerpt projections defined in {@link RepositoryResourceMappings} are + * registered with the {@link RepositoryRestConfiguration}. This rather external configuration has been used to make + * sure we don't introduce a cyclic dependency between {@link RepositoryRestConfiguration} an + * {@link RepositoryResourceMappings} as the latter need access to the former to discover mappings in the first place. + * + * @author Oliver Gierke + * @since 2.5 + * @soundtrack Katinka - Ausverkauf + */ +public class ProjectionDefinitionRegistar extends InstantiationAwareBeanPostProcessorAdapter { + + private final ObjectFactory config; + + /** + * Creates a new {@link ProjectionDefinitionRegistar} for the given {@link RepositoryRestConfiguration}. + * + * @param config must not be {@literal null}. + */ + public ProjectionDefinitionRegistar(ObjectFactory config) { + + Assert.notNull(config, "RepositoryRestConfiguration must not be null!"); + + this.config = config; + } + + /* + * (non-Javadoc) + * @see org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter#postProcessAfterInitialization(java.lang.Object, java.lang.String) + */ + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { + + if (!(bean instanceof ResourceMappings)) { + return bean; + } + + ResourceMappings mappings = (ResourceMappings) bean; + + for (ResourceMetadata resourceMetadata : mappings) { + + Class projection = resourceMetadata.getExcerptProjection(); + + if (projection != null) { + config.getObject().getProjectionConfiguration().addProjection(projection); + } + } + + return bean; + } +} 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 4297137cc..5e0adce8b 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 @@ -244,7 +244,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon @Bean public RepositoryRestConfiguration config() { - ProjectionDefinitionConfiguration configuration = new ProjectionDefinitionConfiguration(resourceMappings()); + ProjectionDefinitionConfiguration configuration = new ProjectionDefinitionConfiguration(); // Register projections found in packages for (Class projection : getProjections(repositories())) { @@ -259,6 +259,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon return config; } + @Bean + public ProjectionDefinitionRegistar projectionDefinitionRegistrar(ObjectFactory config) { + return new ProjectionDefinitionRegistar(config); + } + @Bean public MetadataConfiguration metadataConfiguration() { return new MetadataConfiguration(); @@ -568,8 +573,9 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon } @Bean - public ResourceMappings resourceMappings() { - return new RepositoryResourceMappings(repositories(), persistentEntities()); + public RepositoryResourceMappings resourceMappings() { + return new RepositoryResourceMappings(repositories(), persistentEntities(), + config().getRepositoryDetectionStrategy()); } /** diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java index 09e048be1..6840fa4c9 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/RepositoryTestsConfig.java @@ -108,7 +108,8 @@ public class RepositoryTestsConfig { @Bean public Module persistentEntityModule() { - RepositoryResourceMappings mappings = new RepositoryResourceMappings(repositories(), persistentEntities()); + RepositoryResourceMappings mappings = new RepositoryResourceMappings(repositories(), persistentEntities(), + config().getRepositoryDetectionStrategy()); EntityLinks entityLinks = new RepositoryEntityLinks(repositories(), mappings, config(), mock(PagingAndSortingTemplateVariables.class), OrderAwarePluginRegistry., BackendIdConverter> create(Arrays.asList(DefaultIdConverter.INSTANCE)));