DATAREST-473 - Introduced configuration option to control default exposure of repositories.

RepositoryRestConfiguration now exposes a setRepositoryDetectionStrategy(…) to define which repositories should be detected for exposure by default. The default value for that will consider the repository interfaces visibility but also take the exported flag of @(Repository)RestResource into account. See all other options in RepositoryDetectionStrategies.

Tweaked the auto-registration of excerpt projections to avoid a circular dependency between configuration and resource mappings and moved it onto a BeanPostProcessor implementation.
This commit is contained in:
Oliver Gierke
2015-11-25 11:32:34 +01:00
parent b663b5ff73
commit dc36dfb67a
13 changed files with 416 additions and 85 deletions

View File

@@ -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.<ResourceMetadata> emptySet());
}
/**
* Creates a new {@link ProjectionDefinitionConfiguration} from the given {@link ResourceMetadata} instances.
*
* @param resourceMetadata must not be {@literal null}.
*/
public ProjectionDefinitionConfiguration(Iterable<ResourceMetadata> resourceMetadata) {
Assert.notNull(resourceMetadata, "ResourceMetadata must not be null!");
this.projectionDefinitions = new HashMap<ProjectionDefinitionKey, Class<?>>();
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!");

View File

@@ -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<Class<?>> exposeIdsFor = new ArrayList<Class<?>>();
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;
}
}

View File

@@ -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;
}
/*

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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.<String, Class<?>> hasEntry("name", SampleProjection.class));
}
@Projection(name = "name", types = Integer.class)
interface SampleProjection {}

View File

@@ -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 {}

View File

@@ -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<Class<?>, 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<Class<?>, 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<Class<?>, 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<Class<?>, Boolean>() {
{
put(AnnotatedRepository.class, true);
put(HiddenRepository.class, false);
put(PublicRepository.class, false);
put(PackageProtectedRepository.class, false);
}
});
}
private static void assertExposures(RepositoryDetectionStrategy strategy, Map<Class<?>, Boolean> expected) {
for (Entry<Class<?>, Boolean> entry : expected.entrySet()) {
assertThat(strategy.isExported(new DefaultRepositoryMetadata(entry.getKey())), is(entry.getValue()));
}
}
interface PackageProtectedRepository extends Repository<Object, Long> {}
public interface PublicRepository extends Repository<Object, Long> {}
@RepositoryRestResource
interface AnnotatedRepository extends Repository<Object, Long> {}
@RepositoryRestResource(exported = false)
public interface HiddenRepository extends Repository<Object, Long> {}
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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<RepositoryRestConfiguration> config;
/**
* Creates a new {@link ProjectionDefinitionRegistar} for the given {@link RepositoryRestConfiguration}.
*
* @param config must not be {@literal null}.
*/
public ProjectionDefinitionRegistar(ObjectFactory<RepositoryRestConfiguration> 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;
}
}

View File

@@ -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<RepositoryRestConfiguration> 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());
}
/**

View File

@@ -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.<Class<?>, BackendIdConverter> create(Arrays.asList(DefaultIdConverter.INSTANCE)));