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; package org.springframework.data.rest.core.config;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import org.springframework.core.annotation.AnnotationUtils; 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.data.rest.core.projection.ProjectionDefinitions;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@@ -44,28 +42,7 @@ public class ProjectionDefinitionConfiguration implements ProjectionDefinitions
* Creates a new {@link ProjectionDefinitionConfiguration}. * Creates a new {@link ProjectionDefinitionConfiguration}.
*/ */
public 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<?>>(); 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(); String name = annotation.name();
Class<?>[] sourceTypes = annotation.types(); Class<?>[] sourceTypes = annotation.types();
return StringUtils.hasText(name) ? addProjection(projectionType, name, sourceTypes) : addProjection(projectionType, return StringUtils.hasText(name) ? addProjection(projectionType, name, sourceTypes)
sourceTypes); : addProjection(projectionType, sourceTypes);
} }
/** /**
@@ -132,7 +109,8 @@ public class ProjectionDefinitionConfiguration implements ProjectionDefinitions
* @param sourceTypes must not be {@literal null} or empty. * @param sourceTypes must not be {@literal null} or empty.
* @return * @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.notNull(projectionType, "Projection type must not be null!");
Assert.hasText(name, "Name must not be null or empty!"); 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.Collections;
import java.util.List; 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.hateoas.MediaTypes;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@@ -52,10 +54,12 @@ public class RepositoryRestConfiguration {
private List<Class<?>> exposeIdsFor = new ArrayList<Class<?>>(); private List<Class<?>> exposeIdsFor = new ArrayList<Class<?>>();
private ResourceMappingConfiguration domainMappings = new ResourceMappingConfiguration(); private ResourceMappingConfiguration domainMappings = new ResourceMappingConfiguration();
private ResourceMappingConfiguration repoMappings = new ResourceMappingConfiguration(); private ResourceMappingConfiguration repoMappings = new ResourceMappingConfiguration();
private RepositoryDetectionStrategy repositoryDetectionStrategy = RepositoryDetectionStrategies.DEFAULT;
private final ProjectionDefinitionConfiguration projectionConfiguration; private final ProjectionDefinitionConfiguration projectionConfiguration;
private final MetadataConfiguration metadataConfiguration; private final MetadataConfiguration metadataConfiguration;
private final EnumTranslationConfiguration enumSerializationConfiguration; private final EnumTranslationConfiguration enumTranslationConfiguration;
private boolean enableEnumTranslation = false; private boolean enableEnumTranslation = false;
/** /**
@@ -63,17 +67,18 @@ public class RepositoryRestConfiguration {
* *
* @param projectionConfiguration must not be {@literal null}. * @param projectionConfiguration must not be {@literal null}.
* @param metadataConfiguration must not be {@literal null}. * @param metadataConfiguration must not be {@literal null}.
* @param enumTranslationConfiguration must not be {@literal null}.
*/ */
public RepositoryRestConfiguration(ProjectionDefinitionConfiguration projectionConfiguration, public RepositoryRestConfiguration(ProjectionDefinitionConfiguration projectionConfiguration,
MetadataConfiguration metadataConfiguration, EnumTranslationConfiguration enumTranslationConfiguration) { MetadataConfiguration metadataConfiguration, EnumTranslationConfiguration enumTranslationConfiguration) {
Assert.notNull(projectionConfiguration, "ProjectionDefinitionConfiguration must not be null!"); Assert.notNull(projectionConfiguration, "ProjectionDefinitionConfiguration must not be null!");
Assert.notNull(metadataConfiguration, "MetadataConfiguration 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.projectionConfiguration = projectionConfiguration;
this.metadataConfiguration = metadataConfiguration; this.metadataConfiguration = metadataConfiguration;
this.enumSerializationConfiguration = enumTranslationConfiguration; this.enumTranslationConfiguration = enumTranslationConfiguration;
} }
/** /**
@@ -489,7 +494,7 @@ public class RepositoryRestConfiguration {
* details see {@link EnumTranslator}. * details see {@link EnumTranslator}.
* *
* @param enableEnumTranslation * @param enableEnumTranslation
* @see #getEnumSerializationConfiguration() * @see #getEnumTranslationConfiguration()
*/ */
public void setEnableEnumTranslation(boolean enableEnumTranslation) { public void setEnableEnumTranslation(boolean enableEnumTranslation) {
this.enableEnumTranslation = enableEnumTranslation; this.enableEnumTranslation = enableEnumTranslation;
@@ -499,17 +504,43 @@ public class RepositoryRestConfiguration {
* Returns whether enum value translation is enabled. * Returns whether enum value translation is enabled.
* *
* @return * @return
* @since 2.4
*/ */
public boolean isEnableEnumTranslation() { public boolean isEnableEnumTranslation() {
return this.enableEnumTranslation; 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() { public EnumTranslationConfiguration getEnumTranslationConfiguration() {
return this.enumSerializationConfiguration; 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; package org.springframework.data.rest.core.mapping;
import java.lang.reflect.Modifier;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.AnnotationUtils;
@@ -45,11 +43,11 @@ class RepositoryCollectionResourceMapping implements CollectionResourceMapping {
private final RestResource annotation; private final RestResource annotation;
private final RepositoryRestResource repositoryAnnotation; private final RepositoryRestResource repositoryAnnotation;
private final CollectionResourceMapping domainTypeMapping; private final CollectionResourceMapping domainTypeMapping;
private final boolean repositoryIsExportCandidate; private final boolean repositoryExported;
private final RepositoryMetadata metadata; private final RepositoryMetadata metadata;
public RepositoryCollectionResourceMapping(RepositoryMetadata metadata) { public RepositoryCollectionResourceMapping(RepositoryMetadata metadata, RepositoryDetectionStrategy strategy) {
this(metadata, new EvoInflectorRelProvider()); this(metadata, new EvoInflectorRelProvider(), strategy);
} }
/** /**
@@ -58,22 +56,26 @@ class RepositoryCollectionResourceMapping implements CollectionResourceMapping {
* *
* @param repositoryType must not be {@literal null}. * @param repositoryType must not be {@literal null}.
* @param relProvider 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(metadata, "Repository metadata must not be null!");
Assert.notNull(relProvider, "RelProvider must not be null!"); Assert.notNull(relProvider, "RelProvider must not be null!");
Assert.notNull(strategy, "RepositoryDetectionStrategy must not be null!");
Class<?> repositoryType = metadata.getRepositoryInterface(); Class<?> repositoryType = metadata.getRepositoryInterface();
this.metadata = metadata; this.metadata = metadata;
this.annotation = AnnotationUtils.findAnnotation(repositoryType, RestResource.class); this.annotation = AnnotationUtils.findAnnotation(repositoryType, RestResource.class);
this.repositoryAnnotation = AnnotationUtils.findAnnotation(repositoryType, RepositoryRestResource.class); this.repositoryAnnotation = AnnotationUtils.findAnnotation(repositoryType, RepositoryRestResource.class);
this.repositoryIsExportCandidate = Modifier.isPublic(repositoryType.getModifiers()); this.repositoryExported = strategy.isExported(metadata);
Class<?> domainType = metadata.getDomainType(); Class<?> domainType = metadata.getDomainType();
this.domainTypeMapping = EVO_INFLECTOR_IS_PRESENT ? new EvoInflectorTypeBasedCollectionResourceMapping(domainType, this.domainTypeMapping = EVO_INFLECTOR_IS_PRESENT
relProvider) : new TypeBasedCollectionResourceMapping(domainType, relProvider); ? new EvoInflectorTypeBasedCollectionResourceMapping(domainType, relProvider)
: new TypeBasedCollectionResourceMapping(domainType, relProvider);
if (annotation != null) { if (annotation != null) {
LOGGER.warn( LOGGER.warn(
@@ -149,16 +151,7 @@ class RepositoryCollectionResourceMapping implements CollectionResourceMapping {
*/ */
@Override @Override
public boolean isExported() { public boolean isExported() {
return repositoryExported;
if (repositoryAnnotation != null) {
return repositoryAnnotation.exported();
}
if (annotation != null) {
return annotation.exported();
}
return repositoryIsExportCandidate && domainTypeMapping.isExported();
} }
/* /*

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 repositories must not be {@literal null}.
* @param entities 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) { public RepositoryResourceMappings(Repositories repositories, PersistentEntities entities,
this(repositories, entities, new EvoInflectorRelProvider()); 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 repositories must not be {@literal null}.
* @param entities must not be {@literal null}. * @param entities must not be {@literal null}.
* @param relProvider 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); super(entities);
Assert.notNull(repositories, "Repositories must not be null!"); Assert.notNull(repositories, "Repositories must not be null!");
Assert.notNull(strategy, "RepositoryDetectionStrategy must not be null!");
this.repositories = repositories; 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) { for (Class<?> type : repositories) {
@@ -79,7 +85,8 @@ public class RepositoryResourceMappings extends PersistentEntitiesResourceMappin
Class<?> repositoryInterface = repositoryInformation.getRepositoryInterface(); Class<?> repositoryInterface = repositoryInformation.getRepositoryInterface();
PersistentEntity<?, ?> entity = repositories.getPersistentEntity(type); 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, RepositoryAwareResourceMetadata information = new RepositoryAwareResourceMetadata(entity, mapping, this,
repositoryInformation); repositoryInformation);

View File

@@ -17,14 +17,9 @@ package org.springframework.data.rest.core.config;
import static org.hamcrest.Matchers.*; import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.util.Arrays;
import org.hamcrest.Matchers;
import org.junit.Test; import org.junit.Test;
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration.ProjectionDefinitionKey; import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration.ProjectionDefinitionKey;
import org.springframework.data.rest.core.mapping.ResourceMetadata;
/** /**
* Unit tests for {@link ProjectionDefinitionConfiguration}. * Unit tests for {@link ProjectionDefinitionConfiguration}.
@@ -155,19 +150,6 @@ public class ProjectionDefinitionConfigurationUnitTests {
is(typeCompatibleWith(ParentProjection.class))); 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) @Projection(name = "name", types = Integer.class)
interface SampleProjection {} 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.Path;
import org.springframework.data.rest.core.annotation.RepositoryRestResource; import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource; import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.RepositoryDetectionStrategies;
/** /**
* Unit tests for {@link RepositoryCollectionResourceMapping}. * 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"))); assertThat(mapping.getPath(), is(new Path("/objects")));
} }
@@ -113,7 +115,7 @@ public class RepositoryCollectionResourceMappingUnitTests {
private static CollectionResourceMapping getResourceMappingFor(Class<?> repositoryInterface) { private static CollectionResourceMapping getResourceMappingFor(Class<?> repositoryInterface) {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface); RepositoryMetadata metadata = new DefaultRepositoryMetadata(repositoryInterface);
return new RepositoryCollectionResourceMapping(metadata); return new RepositoryCollectionResourceMapping(metadata, RepositoryDetectionStrategies.DEFAULT);
} }
public static class Person {} 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.repository.query.Param;
import org.springframework.data.rest.core.Path; import org.springframework.data.rest.core.Path;
import org.springframework.data.rest.core.annotation.RestResource; import org.springframework.data.rest.core.annotation.RestResource;
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.RepositoryDetectionStrategies;
/** /**
* Unit tests for {@link RepositoryMethodResourceMapping}. * Unit tests for {@link RepositoryMethodResourceMapping}.
@@ -39,7 +40,8 @@ import org.springframework.data.rest.core.annotation.RestResource;
public class RepositoryMethodResourceMappingUnitTests { public class RepositoryMethodResourceMappingUnitTests {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(PersonRepository.class); RepositoryMetadata metadata = new DefaultRepositoryMetadata(PersonRepository.class);
RepositoryCollectionResourceMapping resourceMapping = new RepositoryCollectionResourceMapping(metadata); RepositoryCollectionResourceMapping resourceMapping = new RepositoryCollectionResourceMapping(metadata,
RepositoryDetectionStrategies.DEFAULT);
@Test @Test
public void defaultsMappingToMethodName() throws Exception { 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.CreditCard;
import org.springframework.data.rest.core.domain.jpa.JpaRepositoryConfig; import org.springframework.data.rest.core.domain.jpa.JpaRepositoryConfig;
import org.springframework.data.rest.core.domain.jpa.Person; 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.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -62,7 +64,8 @@ public class RepositoryResourceMappingsIntegrationTests {
public void setUp() { public void setUp() {
Repositories repositories = new Repositories(factory); 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 @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 @Bean
public RepositoryRestConfiguration config() { public RepositoryRestConfiguration config() {
ProjectionDefinitionConfiguration configuration = new ProjectionDefinitionConfiguration(resourceMappings()); ProjectionDefinitionConfiguration configuration = new ProjectionDefinitionConfiguration();
// Register projections found in packages // Register projections found in packages
for (Class<?> projection : getProjections(repositories())) { for (Class<?> projection : getProjections(repositories())) {
@@ -259,6 +259,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
return config; return config;
} }
@Bean
public ProjectionDefinitionRegistar projectionDefinitionRegistrar(ObjectFactory<RepositoryRestConfiguration> config) {
return new ProjectionDefinitionRegistar(config);
}
@Bean @Bean
public MetadataConfiguration metadataConfiguration() { public MetadataConfiguration metadataConfiguration() {
return new MetadataConfiguration(); return new MetadataConfiguration();
@@ -568,8 +573,9 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
} }
@Bean @Bean
public ResourceMappings resourceMappings() { public RepositoryResourceMappings resourceMappings() {
return new RepositoryResourceMappings(repositories(), persistentEntities()); return new RepositoryResourceMappings(repositories(), persistentEntities(),
config().getRepositoryDetectionStrategy());
} }
/** /**

View File

@@ -108,7 +108,8 @@ public class RepositoryTestsConfig {
@Bean @Bean
public Module persistentEntityModule() { public Module persistentEntityModule() {
RepositoryResourceMappings mappings = new RepositoryResourceMappings(repositories(), persistentEntities()); RepositoryResourceMappings mappings = new RepositoryResourceMappings(repositories(), persistentEntities(),
config().getRepositoryDetectionStrategy());
EntityLinks entityLinks = new RepositoryEntityLinks(repositories(), mappings, config(), EntityLinks entityLinks = new RepositoryEntityLinks(repositories(), mappings, config(),
mock(PagingAndSortingTemplateVariables.class), mock(PagingAndSortingTemplateVariables.class),
OrderAwarePluginRegistry.<Class<?>, BackendIdConverter> create(Arrays.asList(DefaultIdConverter.INSTANCE))); OrderAwarePluginRegistry.<Class<?>, BackendIdConverter> create(Arrays.asList(DefaultIdConverter.INSTANCE)));