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:
@@ -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!");
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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 {}
|
||||||
|
|
||||||
|
|||||||
@@ -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 {}
|
||||||
|
|||||||
@@ -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> {}
|
||||||
|
}
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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)));
|
||||||
|
|||||||
Reference in New Issue
Block a user