DATAREST-724 - Added configuration API for entity lookup customizations.
RepositoryRestConfiguration now exposes a withCustomEntityLookup() returning an EntityLookupRegistrar that allows to define customizations as follows on Java 8: config.withCustomEntityLookup() .forRepository(UserRepository.class) .withIdMapping(User::getUsername) .withLookup(UserRepository::findByUsername); or even abbreviated to: config.withCustomEntityLookup() .forRepository(UserRepository.class, User::getUsername, UserRepository::findByUsername); The API basically takes two lambdas or method references to define the id mapping (User::getUsername in this case) as well as the lookup call based on the repository type with which the customization builder was set up in the first place (UserRepository::findByUsername in this case).
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.repository.core.RepositoryInformation;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.config.EntityLookupRegistrar.LookupRegistrar.Lookup;
|
||||
import org.springframework.data.rest.core.support.EntityLookup;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Configuration instance to implement {@link EntityLookupRegistrar}. Exposed via
|
||||
* {@link RepositoryRestConfiguration#withCustomEntityLookup()}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.5
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
class EntityLookupConfiguration implements EntityLookupRegistrar {
|
||||
|
||||
private final List<LookupInformation<Object, Serializable, Repository<? extends Object, ?>>> lookupInformation = new ArrayList<LookupInformation<Object, Serializable, Repository<?, ?>>>();
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.config.EntityLookupRegistrar#forRepository(java.lang.Class, org.springframework.core.convert.converter.Converter, org.springframework.data.rest.core.config.EntityLookupRegistrar.LookupRegistrar.Lookup)
|
||||
*/
|
||||
@Override
|
||||
public <T, ID extends Serializable, R extends Repository<T, ?>> EntityLookupRegistrar forRepository(
|
||||
Class<R> repositoryType, Converter<T, ID> converter, Lookup<R, ID> lookup) {
|
||||
|
||||
new MappingBuilder<T, ID, R>(repositoryType).withIdMapping(converter).withLookup(lookup);
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.config.EntityLookupRegistrar#forRepository(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T, ID extends Serializable, R extends Repository<T, ?>> IdMappingRegistrar<T, R> forRepository(
|
||||
Class<R> type) {
|
||||
return new MappingBuilder<T, ID, R>(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom builder implementation to back {@link LookupRegistrar} and {@link IdMappingRegistrar}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
private class MappingBuilder<T, ID extends Serializable, R extends Repository<T, ?>>
|
||||
implements LookupRegistrar<T, ID, R>, IdMappingRegistrar<T, R> {
|
||||
|
||||
private @NonNull final Class<R> repositoryType;
|
||||
private Converter<T, ID> idMapping;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MappingBuilder} using the given repository type and identifier mapping.
|
||||
*
|
||||
* @param repositoryType must not be {@literal null}.
|
||||
* @param mapping must not be {@literal null}.
|
||||
*/
|
||||
private MappingBuilder(Class<R> repositoryType, Converter<T, ID> mapping) {
|
||||
|
||||
this(repositoryType);
|
||||
|
||||
Assert.notNull(mapping, "Converter must not be null!");
|
||||
|
||||
this.idMapping = mapping;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.config.EntityLookupRegistrar.LookupRegistrar#withLookup(org.springframework.data.rest.core.config.EntityLookupRegistrar.LookupRegistrar.Lookup)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public EntityLookupRegistrar withLookup(Lookup<R, ID> lookup) {
|
||||
|
||||
EntityLookupConfiguration.this.lookupInformation.add(
|
||||
(LookupInformation<Object, Serializable, Repository<? extends Object, ?>>) new LookupInformation<T, ID, R>(
|
||||
repositoryType, idMapping, lookup));
|
||||
|
||||
return EntityLookupConfiguration.this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.config.EntityLookupRegistrar.IdMappingRegistrar#withIdMapping(org.springframework.core.convert.converter.Converter)
|
||||
*/
|
||||
@Override
|
||||
public <ID2 extends Serializable> LookupRegistrar<T, ID2, R> withIdMapping(Converter<T, ID2> idMapping) {
|
||||
return new MappingBuilder<T, ID2, R>(repositoryType, idMapping);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link EntityLookup}s registered on this configuration.
|
||||
*
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public List<EntityLookup<?>> getEntityLookups(Repositories repositories) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
|
||||
List<EntityLookup<?>> lookups = new ArrayList<EntityLookup<?>>(lookupInformation.size());
|
||||
|
||||
for (LookupInformation<Object, Serializable, Repository<? extends Object, ?>> information : lookupInformation) {
|
||||
lookups.add(new RepositoriesEntityLookup<Object>(repositories, information));
|
||||
}
|
||||
|
||||
return lookups;
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link EntityLookup} backed by a repository instance and a {@link LookupInformation}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
private static class RepositoriesEntityLookup<T> implements EntityLookup<T> {
|
||||
|
||||
private final LookupInformation<Object, Serializable, Repository<? extends T, ?>> lookupInfo;
|
||||
private final Repository<? extends T, ?> repository;
|
||||
private final Class<?> domainType;
|
||||
|
||||
/**
|
||||
* Creates a new {@link RepositoriesEntityLookup} for the given {@link Repositories} and {@link LookupInformation}.
|
||||
*
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @param lookupInformation must not be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public RepositoriesEntityLookup(Repositories repositories,
|
||||
LookupInformation<Object, Serializable, Repository<? extends T, ?>> lookupInformation) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(lookupInformation, "LookuInformation must not be null!");
|
||||
|
||||
RepositoryInformation information = repositories.getRepositoryInformation(lookupInformation.repositoryType);
|
||||
|
||||
this.repository = (Repository<? extends T, ?>) repositories.getRepositoryFor(information.getDomainType());
|
||||
this.domainType = information.getDomainType();
|
||||
this.lookupInfo = lookupInformation;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.support.EntityLookup#getResourceIdentifier(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Serializable getResourceIdentifier(T entity) {
|
||||
return lookupInfo.getIdentifierMapping().convert(entity);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.support.EntityLookup#lookupEntity(java.io.Serializable)
|
||||
*/
|
||||
@Override
|
||||
public Object lookupEntity(Serializable id) {
|
||||
return lookupInfo.getLookup().lookup(repository, id);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.plugin.core.Plugin#supports(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean supports(Class<?> delimiter) {
|
||||
return domainType.isAssignableFrom(delimiter);
|
||||
}
|
||||
}
|
||||
|
||||
@Value
|
||||
private static class LookupInformation<T, ID extends Serializable, R extends Repository<? extends T, ?>> {
|
||||
|
||||
private final Class<R> repositoryType;
|
||||
private final Converter<T, ID> identifierMapping;
|
||||
private final Lookup<R, ID> lookup;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.config;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.repository.Repository;
|
||||
import org.springframework.data.rest.core.config.EntityLookupRegistrar.LookupRegistrar.Lookup;
|
||||
import org.springframework.data.rest.core.support.EntityLookup;
|
||||
|
||||
/**
|
||||
* Configuration interfaces to ease the configuration of custom {@link EntityLookup}s for repositories.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.5
|
||||
*/
|
||||
public interface EntityLookupRegistrar {
|
||||
|
||||
/**
|
||||
* Starts building a custom {@link EntityLookup} for the given repository type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
<T, ID extends Serializable, R extends Repository<T, ?>> IdMappingRegistrar<T, R> forRepository(Class<R> type);
|
||||
|
||||
/**
|
||||
* Registers an {@link EntityLookup} for the given repository type, identifier mapping and lookup operation.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param identifierMapping must not be {@literal null}.
|
||||
* @param lookup must not be {@literal null}.
|
||||
*/
|
||||
<T, ID extends Serializable, R extends Repository<T, ?>> EntityLookupRegistrar forRepository(Class<R> type,
|
||||
Converter<T, ID> identifierMapping, Lookup<R, ID> lookup);
|
||||
|
||||
interface IdMappingRegistrar<T, R extends Repository<T, ?>> {
|
||||
|
||||
/**
|
||||
* Registers the given {@link Converter} to map the entity to its identifying property.
|
||||
*
|
||||
* @param mapping must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
<ID extends Serializable> LookupRegistrar<T, ID, R> withIdMapping(Converter<T, ID> mapping);
|
||||
}
|
||||
|
||||
interface LookupRegistrar<T, ID extends Serializable, R extends Repository<T, ?>> {
|
||||
|
||||
/**
|
||||
* Registers the given {@link Lookup} to obtain entity instances.
|
||||
*
|
||||
* @param lookup must not be {@literal null}.
|
||||
*/
|
||||
EntityLookupRegistrar withLookup(Lookup<R, ID> lookup);
|
||||
|
||||
interface Lookup<R extends Repository<? extends Object, ?>, ID> {
|
||||
|
||||
/**
|
||||
* Looks up the entity using the given {@link Repository} and identifier.
|
||||
*
|
||||
* @param repository
|
||||
* @param identifier
|
||||
* @return
|
||||
*/
|
||||
Object lookup(R repository, ID identifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,10 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy;
|
||||
import org.springframework.data.rest.core.mapping.RepositoryDetectionStrategy.RepositoryDetectionStrategies;
|
||||
import org.springframework.data.rest.core.support.EntityLookup;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -58,6 +60,7 @@ public class RepositoryRestConfiguration {
|
||||
|
||||
private final ProjectionDefinitionConfiguration projectionConfiguration;
|
||||
private final MetadataConfiguration metadataConfiguration;
|
||||
private final EntityLookupConfiguration entityLookupConfiguration;
|
||||
|
||||
private final EnumTranslationConfiguration enumTranslationConfiguration;
|
||||
private boolean enableEnumTranslation = false;
|
||||
@@ -79,6 +82,7 @@ public class RepositoryRestConfiguration {
|
||||
this.projectionConfiguration = projectionConfiguration;
|
||||
this.metadataConfiguration = metadataConfiguration;
|
||||
this.enumTranslationConfiguration = enumTranslationConfiguration;
|
||||
this.entityLookupConfiguration = new EntityLookupConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -543,4 +547,28 @@ public class RepositoryRestConfiguration {
|
||||
this.repositoryDetectionStrategy = repositoryDetectionStrategy == null ? RepositoryDetectionStrategies.DEFAULT
|
||||
: repositoryDetectionStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link EntityLookupRegistrar} to create custom {@link EntityLookup} instances registered in the
|
||||
* configuration.
|
||||
*
|
||||
* @return the {@link EntityLookupRegistrar} to build custom {@link EntityLookup}s.
|
||||
* @since 2.5
|
||||
*/
|
||||
public EntityLookupRegistrar withCustomEntityLookup() {
|
||||
return entityLookupConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link EntityLookup}s considering the customizations made to the configuration.
|
||||
*
|
||||
* @param repositories must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public List<EntityLookup<?>> getEntityLookups(Repositories repositories) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
|
||||
return entityLookupConfiguration.getEntityLookups(repositories);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,7 +630,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
public RepositoryInvokerFactory repositoryInvokerFactory() {
|
||||
|
||||
return new UnwrappingRepositoryInvokerFactory(
|
||||
new DefaultRepositoryInvokerFactory(repositories(), defaultConversionService()), lookups);
|
||||
new DefaultRepositoryInvokerFactory(repositories(), defaultConversionService()), getEntityLookups());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -715,7 +715,16 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
|
||||
@Bean
|
||||
public SelfLinkProvider selfLinkProvider() {
|
||||
return new DefaultSelfLinkProvider(persistentEntities(), entityLinks(), lookups);
|
||||
return new DefaultSelfLinkProvider(persistentEntities(), entityLinks(), getEntityLookups());
|
||||
}
|
||||
|
||||
protected List<EntityLookup<?>> getEntityLookups() {
|
||||
|
||||
List<EntityLookup<?>> lookups = new ArrayList<EntityLookup<?>>();
|
||||
lookups.addAll(config().getEntityLookups(repositories()));
|
||||
lookups.addAll(this.lookups);
|
||||
|
||||
return lookups;
|
||||
}
|
||||
|
||||
protected List<HandlerMethodArgumentResolver> defaultMethodArgumentResolvers() {
|
||||
|
||||
Reference in New Issue
Block a user