DATAREST-1304 - EntityLookupConfiguration is now used in PUT for creation.

Previously the backend identifier derived from the URI was set as identifier on the object to be created in a PUT request. However, in case a custom entity lookup is used, this is not the identifier but an arbitrary property pointed to through user defined code, mostly a method reference.

We now use the newly introduced MethodInvocationRecorder API in Spring Data Commons to be able to obtain the property that is supposed to be used and set that to the value calculated. This requires the entity type for which the custom lookup is configured to be non-final as the invocation recording is build on top of proxies.

Related tickets: DATACMNS-1449.
This commit is contained in:
Oliver Drotbohm
2018-12-20 16:25:35 +01:00
parent 6d0acfd997
commit dc10166679
13 changed files with 167 additions and 67 deletions

View File

@@ -15,6 +15,7 @@
*/
package org.springframework.data.rest.core.config;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Value;
@@ -30,6 +31,7 @@ import org.springframework.data.repository.core.support.AbstractRepositoryMetada
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.data.util.MethodInvocationRecorder;
import org.springframework.data.util.StreamUtils;
import org.springframework.util.Assert;
@@ -169,6 +171,7 @@ class EntityLookupConfiguration implements EntityLookupRegistrar {
private final LookupInformation<Object, Object, Repository<? extends T, ?>> lookupInfo;
private final Repository<? extends T, ?> repository;
private final Class<?> domainType;
private final @Getter Optional<String> lookupProperty;
/**
* Creates a new {@link RepositoriesEntityLookup} for the given {@link Repositories} and {@link LookupInformation}.
@@ -192,6 +195,11 @@ class EntityLookupConfiguration implements EntityLookupRegistrar {
this.repository = (Repository<? extends T, ?>) repositories.getRepositoryFor(information.getDomainType())//
.orElseThrow(() -> new IllegalStateException(
"No repository found for type " + information.getDomainType().getName() + "!"));
this.lookupProperty = Optional.of(domainType) //
.flatMap(it -> MethodInvocationRecorder.forProxyOf(it) //
.record(lookupInfo.identifierMapping::convert) //
.getPropertyPath());
}
/*

View File

@@ -125,7 +125,8 @@ public class ExposureConfiguration implements ExposureConfigurer {
}
/**
* Returns whether PUT is supported for the given {@link ResourceMetadata}.
* Returns whether PUT requests can be used to create new instances for the type backing the given
* {@link ResourceMetadata}.
*
* @param metadata must not be {@literal null}.
* @return
@@ -134,7 +135,20 @@ public class ExposureConfiguration implements ExposureConfigurer {
Assert.notNull(metadata, "ResourceMetadata must not be null!");
return creationViaPut.apply(metadata.getDomainType());
return allowsPutForCreation(metadata.getDomainType());
}
/**
* Returns whether PUT requests can be used to create new instances of the given domain type.
*
* @param metadata must not be {@literal null}.
* @return
*/
public boolean allowsPutForCreation(Class<?> domainType) {
Assert.notNull(domainType, "Domain type must not be null!");
return creationViaPut.apply(domainType);
}
HttpMethods filter(ConfigurableHttpMethods methods, ResourceType type, ResourceMetadata metadata) {

View File

@@ -55,4 +55,12 @@ public interface EntityLookup<T> extends Plugin<Class<?>> {
* @return can be {@literal null}.
*/
Optional<T> lookupEntity(Object id);
/**
* Returns the lookup property if available. If {@link Optional#empty()} is returned, we assume the identifier
* property is the one to be used for lookup.
*
* @return will never be {@literal null}.
*/
Optional<String> getLookupProperty();
}

View File

@@ -19,9 +19,15 @@ import static org.assertj.core.api.Assertions.*;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.support.Repositories;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.data.rest.core.config.ResourceMapping;
import org.springframework.data.rest.core.domain.ConfiguredPersonRepository;
import org.springframework.data.rest.core.domain.Profile;
import org.springframework.data.rest.core.domain.ProfileRepository;
import org.springframework.data.rest.core.support.EntityLookup;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.test.annotation.DirtiesContext;
/**
* Tests to check that {@link ResourceMapping}s are handled correctly.
@@ -33,6 +39,7 @@ import org.springframework.data.rest.core.domain.ConfiguredPersonRepository;
public class RepositoryRestConfigurationIntegrationTests extends AbstractIntegrationTests {
@Autowired RepositoryRestConfiguration config;
@Autowired Repositories repositories;
@Test
public void shouldProvideResourceMappingForConfiguredRepository() throws Exception {
@@ -44,4 +51,20 @@ public class RepositoryRestConfigurationIntegrationTests extends AbstractIntegra
assertThat(mapping.getPath()).isEqualTo("people");
assertThat(mapping.isExported()).isFalse();
}
@Test // DATAREST-1304
@DirtiesContext
public void exposesLookupPropertyFromLambda() {
config.withEntityLookup() //
.forRepository(ProfileRepository.class) //
.withIdMapping(Profile::getName) //
.withLookup(ProfileRepository::findByName);
PluginRegistry<EntityLookup<?>, Class<?>> lookups = PluginRegistry.of(config.getEntityLookups(repositories));
assertThat(lookups.getPluginFor(Profile.class)).hasValueSatisfying(it -> {
assertThat(it.getLookupProperty()).hasValue("name");
});
}
}

View File

@@ -16,6 +16,7 @@
package org.springframework.data.rest.core.domain;
import lombok.Value;
import lombok.experimental.NonFinal;
import java.util.UUID;
@@ -25,6 +26,7 @@ import org.springframework.data.annotation.Id;
* @author Jon Brisbin
* @author Oliver Gierke
*/
@NonFinal
@Value
public class Profile {

View File

@@ -25,4 +25,7 @@ import org.springframework.data.repository.CrudRepository;
* @author Jon Brisbin
* @author Oliver Gierke
*/
public interface ProfileRepository extends CrudRepository<Profile, UUID> {}
public interface ProfileRepository extends CrudRepository<Profile, UUID> {
Profile findByName(String name);
}