From 5ae838e55463833a85fc9c3c6a8e4d2ab076ad0b Mon Sep 17 00:00:00 2001 From: Tristan Lins Date: Sun, 18 Apr 2021 15:42:55 +0200 Subject: [PATCH] Implement Key Generation Strategy --- spring-data-mock/pom.xml | 4 +- .../data/domain/KeyGenerationStrategy.java | 14 ++++ .../spring/data/domain/KeyGeneratorAware.java | 2 + .../dsl/factory/RepositoryFactoryBuilder.java | 27 +++++-- .../spring/data/dsl/mock/KeyGeneration.java | 24 ++++++ .../data/dsl/mock/RepositoryMockBuilder.java | 34 +++++--- .../spring/data/proxy/RepositoryFactory.java | 24 +++++- .../proxy/RepositoryFactoryConfiguration.java | 7 ++ .../proxy/impl/DefaultRepositoryFactory.java | 28 ++++--- ...DefaultRepositoryFactoryConfiguration.java | 39 ++++++++- ...mutableRepositoryFactoryConfiguration.java | 35 +++++++- .../repository/CrudRepositorySupport.java | 25 ++++-- ...AllUnamangedKeyGenerationStrategyTest.java | 81 +++++++++++++++++++ .../InformationExposingRepositoryFactory.java | 5 +- 14 files changed, 308 insertions(+), 41 deletions(-) create mode 100644 spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/domain/KeyGenerationStrategy.java create mode 100644 spring-data-mock/src/test/java/com/mmnaseri/utils/spring/data/repository/DefaultCrudRepositoryWithAllUnamangedKeyGenerationStrategyTest.java diff --git a/spring-data-mock/pom.xml b/spring-data-mock/pom.xml index cc6e1c95..34c246e7 100644 --- a/spring-data-mock/pom.xml +++ b/spring-data-mock/pom.xml @@ -27,7 +27,7 @@ com.mmnaseri.utils spring-data-mock - 2.2.0 + 2.2.1 Spring Data Mock A framework for mocking Spring Data repositories @@ -327,4 +327,4 @@ - \ No newline at end of file + diff --git a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/domain/KeyGenerationStrategy.java b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/domain/KeyGenerationStrategy.java new file mode 100644 index 00000000..2e380374 --- /dev/null +++ b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/domain/KeyGenerationStrategy.java @@ -0,0 +1,14 @@ +package com.mmnaseri.utils.spring.data.domain; + +public enum KeyGenerationStrategy { + /** + * Generate a key for {@code null} id values only. + */ + ONLY_NULL, + + /** + * Generate a key for all "unmanaged" entites. + * Regardless of whether an ID value exists or not. + */ + ALL_UNMANAGED +} diff --git a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/domain/KeyGeneratorAware.java b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/domain/KeyGeneratorAware.java index a72f4a10..17dd7c80 100644 --- a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/domain/KeyGeneratorAware.java +++ b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/domain/KeyGeneratorAware.java @@ -10,4 +10,6 @@ public interface KeyGeneratorAware { void setKeyGenerator(KeyGenerator keyGenerator); + void setKeyGenerationStrategy(KeyGenerationStrategy strategy); + } diff --git a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/dsl/factory/RepositoryFactoryBuilder.java b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/dsl/factory/RepositoryFactoryBuilder.java index 48af1d96..0cc0f7ce 100644 --- a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/dsl/factory/RepositoryFactoryBuilder.java +++ b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/dsl/factory/RepositoryFactoryBuilder.java @@ -1,9 +1,6 @@ package com.mmnaseri.utils.spring.data.dsl.factory; -import com.mmnaseri.utils.spring.data.domain.KeyGenerator; -import com.mmnaseri.utils.spring.data.domain.Operator; -import com.mmnaseri.utils.spring.data.domain.OperatorContext; -import com.mmnaseri.utils.spring.data.domain.RepositoryMetadataResolver; +import com.mmnaseri.utils.spring.data.domain.*; import com.mmnaseri.utils.spring.data.domain.impl.DefaultOperatorContext; import com.mmnaseri.utils.spring.data.domain.impl.DefaultRepositoryMetadataResolver; import com.mmnaseri.utils.spring.data.domain.impl.MethodQueryDescriptionExtractor; @@ -64,6 +61,7 @@ public class RepositoryFactoryBuilder private DataStoreEventListenerContext eventListenerContext; private NonDataOperationInvocationHandler operationInvocationHandler; private KeyGenerator defaultKeyGenerator; + private KeyGenerationStrategy defaultKeyGenerationStrategy; private RepositoryFactoryBuilder() { metadataResolver = new DefaultRepositoryMetadataResolver(); @@ -76,6 +74,7 @@ private RepositoryFactoryBuilder() { operationInvocationHandler = new NonDataOperationInvocationHandler(); // by default, we do not want any key generator, unless one is specified defaultKeyGenerator = null; + defaultKeyGenerationStrategy = KeyGenerationStrategy.ONLY_NULL; } @Override @@ -251,17 +250,28 @@ public RepositoryFactoryConfiguration configure() { typeMappingContext, eventListenerContext, operationInvocationHandler, - defaultKeyGenerator); + defaultKeyGenerator, + defaultKeyGenerationStrategy); } @Override public Implementation generateKeysUsing(KeyGenerator keyGenerator) { - return new RepositoryMockBuilder().useFactory(build()).generateKeysUsing(keyGenerator); + return generateKeysUsing(keyGenerator, defaultKeyGenerationStrategy); + } + + @Override + public Implementation generateKeysUsing(KeyGenerator keyGenerator, KeyGenerationStrategy keyGenerationStrategy) { + return new RepositoryMockBuilder().useFactory(build()).generateKeysUsing(keyGenerator, keyGenerationStrategy); } @Override public > Implementation generateKeysUsing(Class generatorType) { - return new RepositoryMockBuilder().useFactory(build()).generateKeysUsing(generatorType); + return generateKeysUsing(generatorType, defaultKeyGenerationStrategy); + } + + @Override + public > Implementation generateKeysUsing(Class generatorType, KeyGenerationStrategy keyGenerationStrategy) { + return new RepositoryMockBuilder().useFactory(build()).generateKeysUsing(generatorType, keyGenerationStrategy); } @Override @@ -291,7 +301,8 @@ public static RepositoryFactoryConfiguration defaultConfiguration() { builder.typeMappingContext, builder.eventListenerContext, builder.operationInvocationHandler, - builder.defaultKeyGenerator); + builder.defaultKeyGenerator, + builder.defaultKeyGenerationStrategy); } /** diff --git a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/dsl/mock/KeyGeneration.java b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/dsl/mock/KeyGeneration.java index 979904a8..20b58951 100644 --- a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/dsl/mock/KeyGeneration.java +++ b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/dsl/mock/KeyGeneration.java @@ -1,5 +1,6 @@ package com.mmnaseri.utils.spring.data.dsl.mock; +import com.mmnaseri.utils.spring.data.domain.KeyGenerationStrategy; import com.mmnaseri.utils.spring.data.domain.KeyGenerator; /** @@ -19,6 +20,17 @@ public interface KeyGeneration extends Implementation { */ Implementation generateKeysUsing(KeyGenerator keyGenerator); + /** + * Sets the key generator to the provided instance + * + * @param keyGenerator the key generator + * @param keyGenerationStrategy the key generation strategy + * @param the type of the keys + * @return the rest of the configuration + */ + Implementation generateKeysUsing( + KeyGenerator keyGenerator, KeyGenerationStrategy keyGenerationStrategy); + /** * Sets the key generator to an instance of the provided type. * @@ -29,6 +41,18 @@ public interface KeyGeneration extends Implementation { */ > Implementation generateKeysUsing(Class generatorType); + /** + * Sets the key generator to an instance of the provided type. + * + * @param generatorType the type of the key generator to use + * @param keyGenerationStrategy the key generation strategy + * @param the type of the keys the generator will be generating + * @param the type of the generator + * @return the rest of the configuration + */ + > Implementation generateKeysUsing( + Class generatorType, KeyGenerationStrategy keyGenerationStrategy); + /** * Tells the builder that we are not going to have any auto-generated keys * diff --git a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/dsl/mock/RepositoryMockBuilder.java b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/dsl/mock/RepositoryMockBuilder.java index 15a885c3..be5e45e6 100644 --- a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/dsl/mock/RepositoryMockBuilder.java +++ b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/dsl/mock/RepositoryMockBuilder.java @@ -1,5 +1,6 @@ package com.mmnaseri.utils.spring.data.dsl.mock; +import com.mmnaseri.utils.spring.data.domain.KeyGenerationStrategy; import com.mmnaseri.utils.spring.data.domain.KeyGenerator; import com.mmnaseri.utils.spring.data.domain.RepositoryMetadata; import com.mmnaseri.utils.spring.data.domain.impl.key.NoOpKeyGenerator; @@ -11,6 +12,7 @@ import java.util.LinkedList; import java.util.List; +import java.util.Optional; /** * This class implements the interfaces used to define a DSL for mocking a repository. @@ -23,34 +25,36 @@ public class RepositoryMockBuilder implements Start, ImplementationAnd, KeyGener private final RepositoryFactory factory; private final List> implementations; private final KeyGenerator keyGenerator; + private final KeyGenerationStrategy keyGenerationStrategy; public RepositoryMockBuilder() { - this(null, new LinkedList<>(), null); + this(null, new LinkedList<>(), null, null); } private RepositoryMockBuilder( - RepositoryFactory factory, List> implementations, KeyGenerator keyGenerator) { + RepositoryFactory factory, List> implementations, KeyGenerator keyGenerator, KeyGenerationStrategy keyGenerationStrategy) { this.factory = factory; this.implementations = implementations; this.keyGenerator = keyGenerator; + this.keyGenerationStrategy = keyGenerationStrategy; } @Override public KeyGeneration useConfiguration(RepositoryFactoryConfiguration configuration) { return new RepositoryMockBuilder( - new DefaultRepositoryFactory(configuration), implementations, keyGenerator); + new DefaultRepositoryFactory(configuration), implementations, keyGenerator, keyGenerationStrategy); } @Override public KeyGeneration useFactory(RepositoryFactory factory) { - return new RepositoryMockBuilder(factory, implementations, keyGenerator); + return new RepositoryMockBuilder(factory, implementations, keyGenerator, keyGenerationStrategy); } @Override public ImplementationAnd usingImplementation(Class implementation) { final LinkedList> implementations = new LinkedList<>(this.implementations); implementations.add(implementation); - return new RepositoryMockBuilder(factory, implementations, keyGenerator); + return new RepositoryMockBuilder(factory, implementations, keyGenerator, keyGenerationStrategy); } @Override @@ -60,19 +64,29 @@ public ImplementationAnd and(Class implementation) { @Override public Implementation generateKeysUsing(KeyGenerator keyGenerator) { - return new RepositoryMockBuilder(factory, implementations, keyGenerator); + return generateKeysUsing(keyGenerator, keyGenerationStrategy); + } + + @Override + public Implementation generateKeysUsing(KeyGenerator keyGenerator, KeyGenerationStrategy keyGenerationStrategy) { + return new RepositoryMockBuilder(factory, implementations, keyGenerator, keyGenerationStrategy); } @Override public > Implementation generateKeysUsing(Class generatorType) { + return generateKeysUsing(generatorType, keyGenerationStrategy); + } + + @Override + public > Implementation generateKeysUsing(Class generatorType, KeyGenerationStrategy keyGenerationStrategy) { //noinspection unchecked final G instance = (G) createKeyGenerator(generatorType); - return generateKeysUsing(instance); + return generateKeysUsing(instance, keyGenerationStrategy); } @Override public Implementation withoutGeneratingKeys() { - return new RepositoryMockBuilder(factory, implementations, new NoOpKeyGenerator<>()); + return new RepositoryMockBuilder(factory, implementations, new NoOpKeyGenerator<>(), keyGenerationStrategy); } private KeyGenerator createKeyGenerator(Class generatorType) { @@ -108,10 +122,10 @@ public E mock(Class repositoryInterface) { generatorProvider.getKeyGenerator(identifierType); evaluatedKeyGenerator = createKeyGenerator(keyGeneratorType); } - return generateKeysUsing(evaluatedKeyGenerator).mock(repositoryInterface); + return generateKeysUsing(evaluatedKeyGenerator, keyGenerationStrategy).mock(repositoryInterface); } else { return repositoryFactory.getInstance( - keyGenerator, repositoryInterface, implementations.toArray(new Class[0])); + keyGenerator, keyGenerationStrategy, repositoryInterface, implementations.toArray(new Class[0])); } } } diff --git a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/RepositoryFactory.java b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/RepositoryFactory.java index c0c893f4..35b20bfe 100644 --- a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/RepositoryFactory.java +++ b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/RepositoryFactory.java @@ -1,5 +1,6 @@ package com.mmnaseri.utils.spring.data.proxy; +import com.mmnaseri.utils.spring.data.domain.KeyGenerationStrategy; import com.mmnaseri.utils.spring.data.domain.KeyGenerator; /** @@ -26,9 +27,30 @@ public interface RepositoryFactory { * @param the type of the interface * @return a prepared instance of the repository * @throws com.mmnaseri.utils.spring.data.error.RepositoryMockException should anything go wrong + * @deprecated Use {@link #getInstance(KeyGenerator, KeyGenerationStrategy, Class, Class[])} instead. + */ + @Deprecated + default E getInstance( + KeyGenerator keyGenerator, Class repositoryInterface, Class... implementations) { + return getInstance(keyGenerator, KeyGenerationStrategy.ONLY_NULL, repositoryInterface, implementations); + } + + /** + * Creates an instance of the repository as per the provided configuration. + * + * @param keyGenerator the key generator to use when inserting items (if auto generation is + * required). You can specify a {@literal null} key generator to signify that {@link + * RepositoryFactoryConfiguration#getDefaultKeyGenerator() the fallback key generator} should + * be used when generating keys. + * @param keyGenerationStrategy the strategy when and for which entities a key should be generated. + * @param repositoryInterface the repository interface which we want to mock + * @param implementations all the concrete classes that can be used to figure out method mappings + * @param the type of the interface + * @return a prepared instance of the repository + * @throws com.mmnaseri.utils.spring.data.error.RepositoryMockException should anything go wrong */ E getInstance( - KeyGenerator keyGenerator, Class repositoryInterface, Class... implementations); + KeyGenerator keyGenerator, KeyGenerationStrategy keyGenerationStrategy, Class repositoryInterface, Class... implementations); /** @return the configuration bound to this repository factory */ RepositoryFactoryConfiguration getConfiguration(); diff --git a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/RepositoryFactoryConfiguration.java b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/RepositoryFactoryConfiguration.java index a531d9f7..eb7383fd 100644 --- a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/RepositoryFactoryConfiguration.java +++ b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/RepositoryFactoryConfiguration.java @@ -1,5 +1,6 @@ package com.mmnaseri.utils.spring.data.proxy; +import com.mmnaseri.utils.spring.data.domain.KeyGenerationStrategy; import com.mmnaseri.utils.spring.data.domain.KeyGenerator; import com.mmnaseri.utils.spring.data.domain.RepositoryMetadataResolver; import com.mmnaseri.utils.spring.data.domain.impl.MethodQueryDescriptionExtractor; @@ -46,4 +47,10 @@ public interface RepositoryFactoryConfiguration { * specified */ KeyGenerator getDefaultKeyGenerator(); + + /** + * @return the default key generation strategy that should be used as a fallback when no strategy is + * specified + */ + KeyGenerationStrategy getDefaultKeyGenerationStrategy(); } diff --git a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/impl/DefaultRepositoryFactory.java b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/impl/DefaultRepositoryFactory.java index c55cf458..be923e92 100644 --- a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/impl/DefaultRepositoryFactory.java +++ b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/impl/DefaultRepositoryFactory.java @@ -1,12 +1,6 @@ package com.mmnaseri.utils.spring.data.proxy.impl; -import com.mmnaseri.utils.spring.data.domain.DataStoreAware; -import com.mmnaseri.utils.spring.data.domain.KeyGenerator; -import com.mmnaseri.utils.spring.data.domain.KeyGeneratorAware; -import com.mmnaseri.utils.spring.data.domain.RepositoryAware; -import com.mmnaseri.utils.spring.data.domain.RepositoryMetadata; -import com.mmnaseri.utils.spring.data.domain.RepositoryMetadataAware; -import com.mmnaseri.utils.spring.data.domain.RepositoryMetadataResolver; +import com.mmnaseri.utils.spring.data.domain.*; import com.mmnaseri.utils.spring.data.domain.impl.MethodQueryDescriptionExtractor; import com.mmnaseri.utils.spring.data.domain.impl.key.NoOpKeyGenerator; import com.mmnaseri.utils.spring.data.proxy.DataOperationResolver; @@ -73,7 +67,7 @@ public DefaultRepositoryFactory(RepositoryFactoryConfiguration configuration) { @Override public E getInstance( - KeyGenerator keyGenerator, Class repositoryInterface, Class... implementations) { + KeyGenerator keyGenerator, KeyGenerationStrategy keyGenerationStrategy, Class repositoryInterface, Class... implementations) { final KeyGenerator actualKeyGenerator; if (keyGenerator == null) { if (configuration.getDefaultKeyGenerator() != null) { @@ -87,6 +81,19 @@ public E getInstance( } else { actualKeyGenerator = keyGenerator; } + final KeyGenerationStrategy actualKeyGenerationStrategy; + if (keyGenerationStrategy == null) { + if (configuration.getDefaultKeyGenerator() != null) { + // if no key generation strategy is passed and there is a default strategy specified, we + // fall back to that + actualKeyGenerationStrategy = configuration.getDefaultKeyGenerationStrategy(); + } else { + // otherwise, fall back to ONLY_NULL + actualKeyGenerationStrategy = KeyGenerationStrategy.ONLY_NULL; + } + } else { + actualKeyGenerationStrategy = keyGenerationStrategy; + } log.info( "We are going to create a proxy instance of type " + repositoryInterface @@ -104,7 +111,7 @@ public E getInstance( log.info( "Trying to find all the proper type mappings for entity repository " + repositoryInterface); final List> typeMappings = - getTypeMappings(metadata, dataStore, actualKeyGenerator, implementations); + getTypeMappings(metadata, dataStore, actualKeyGenerator, actualKeyGenerationStrategy, implementations); // set up the data operation resolver final DataOperationResolver operationResolver = new DefaultDataOperationResolver( @@ -176,6 +183,7 @@ public RepositoryFactoryConfiguration getConfiguration() { * @param metadata the repository metadata * @param dataStore the data store * @param keyGenerator the key generator + * @param keyGenerationStrategy * @param implementations the implementations specified by the user * @return the resolved list of type mappings */ @@ -183,6 +191,7 @@ private List> getTypeMappings( RepositoryMetadata metadata, DataStore dataStore, KeyGenerator keyGenerator, + KeyGenerationStrategy keyGenerationStrategy, Class[] implementations) { final TypeMappingContext localContext = new DefaultTypeMappingContext(typeMappingContext); for (Class implementation : implementations) { @@ -203,6 +212,7 @@ private List> getTypeMappings( KeyGeneratorAware instance = (KeyGeneratorAware) mapping.getInstance(); //noinspection unchecked instance.setKeyGenerator(keyGenerator); + instance.setKeyGenerationStrategy(keyGenerationStrategy); } if (mapping.getInstance() instanceof RepositoryFactoryConfigurationAware) { RepositoryFactoryConfigurationAware instance = diff --git a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/impl/DefaultRepositoryFactoryConfiguration.java b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/impl/DefaultRepositoryFactoryConfiguration.java index 04d7645b..6e9ef1ef 100644 --- a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/impl/DefaultRepositoryFactoryConfiguration.java +++ b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/impl/DefaultRepositoryFactoryConfiguration.java @@ -1,5 +1,6 @@ package com.mmnaseri.utils.spring.data.proxy.impl; +import com.mmnaseri.utils.spring.data.domain.KeyGenerationStrategy; import com.mmnaseri.utils.spring.data.domain.KeyGenerator; import com.mmnaseri.utils.spring.data.domain.RepositoryMetadataResolver; import com.mmnaseri.utils.spring.data.domain.impl.MethodQueryDescriptionExtractor; @@ -27,6 +28,7 @@ public class DefaultRepositoryFactoryConfiguration implements RepositoryFactoryC private DataStoreEventListenerContext eventListenerContext; private NonDataOperationInvocationHandler operationInvocationHandler; private KeyGenerator defaultKeyGenerator; + private KeyGenerationStrategy defaultKeyGenerationStrategy; public DefaultRepositoryFactoryConfiguration() {} @@ -40,9 +42,11 @@ public DefaultRepositoryFactoryConfiguration(RepositoryFactoryConfiguration conf configuration.getTypeMappingContext(), configuration.getEventListenerContext(), configuration.getOperationInvocationHandler(), - configuration.getDefaultKeyGenerator()); + configuration.getDefaultKeyGenerator(), + configuration.getDefaultKeyGenerationStrategy()); } + @Deprecated public DefaultRepositoryFactoryConfiguration( RepositoryMetadataResolver repositoryMetadataResolver, MethodQueryDescriptionExtractor descriptionExtractor, @@ -53,6 +57,29 @@ public DefaultRepositoryFactoryConfiguration( DataStoreEventListenerContext eventListenerContext, NonDataOperationInvocationHandler operationInvocationHandler, KeyGenerator defaultKeyGenerator) { + this(repositoryMetadataResolver, + descriptionExtractor, + functionRegistry, + dataStoreRegistry, + resultAdapterContext, + typeMappingContext, + eventListenerContext, + operationInvocationHandler, + defaultKeyGenerator, + KeyGenerationStrategy.ONLY_NULL); + } + + public DefaultRepositoryFactoryConfiguration( + RepositoryMetadataResolver repositoryMetadataResolver, + MethodQueryDescriptionExtractor descriptionExtractor, + DataFunctionRegistry functionRegistry, + DataStoreRegistry dataStoreRegistry, + ResultAdapterContext resultAdapterContext, + TypeMappingContext typeMappingContext, + DataStoreEventListenerContext eventListenerContext, + NonDataOperationInvocationHandler operationInvocationHandler, + KeyGenerator defaultKeyGenerator, + KeyGenerationStrategy defaultKeyGenerationStrategy) { this.repositoryMetadataResolver = repositoryMetadataResolver; this.descriptionExtractor = descriptionExtractor; this.functionRegistry = functionRegistry; @@ -62,6 +89,7 @@ public DefaultRepositoryFactoryConfiguration( this.eventListenerContext = eventListenerContext; this.operationInvocationHandler = operationInvocationHandler; this.defaultKeyGenerator = defaultKeyGenerator; + this.defaultKeyGenerationStrategy = defaultKeyGenerationStrategy; } @Override @@ -145,4 +173,13 @@ public KeyGenerator getDefaultKeyGenerator() { public void setDefaultKeyGenerator(KeyGenerator defaultKeyGenerator) { this.defaultKeyGenerator = defaultKeyGenerator; } + + @Override + public KeyGenerationStrategy getDefaultKeyGenerationStrategy() { + return defaultKeyGenerationStrategy; + } + + public void setDefaultKeyGenerationStrategy(KeyGenerationStrategy defaultKeyGenerationStrategy) { + this.defaultKeyGenerationStrategy = defaultKeyGenerationStrategy; + } } diff --git a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/impl/ImmutableRepositoryFactoryConfiguration.java b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/impl/ImmutableRepositoryFactoryConfiguration.java index 8dde8e3a..7eddc7ac 100644 --- a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/impl/ImmutableRepositoryFactoryConfiguration.java +++ b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/proxy/impl/ImmutableRepositoryFactoryConfiguration.java @@ -1,5 +1,6 @@ package com.mmnaseri.utils.spring.data.proxy.impl; +import com.mmnaseri.utils.spring.data.domain.KeyGenerationStrategy; import com.mmnaseri.utils.spring.data.domain.KeyGenerator; import com.mmnaseri.utils.spring.data.domain.RepositoryMetadataResolver; import com.mmnaseri.utils.spring.data.domain.impl.MethodQueryDescriptionExtractor; @@ -29,6 +30,7 @@ public class ImmutableRepositoryFactoryConfiguration implements RepositoryFactor private final DataStoreEventListenerContext eventListenerContext; private final NonDataOperationInvocationHandler operationInvocationHandler; private final KeyGenerator keyGenerator; + private final KeyGenerationStrategy keyGenerationStrategy; public ImmutableRepositoryFactoryConfiguration(RepositoryFactoryConfiguration configuration) { this( @@ -40,9 +42,11 @@ public ImmutableRepositoryFactoryConfiguration(RepositoryFactoryConfiguration co configuration.getTypeMappingContext(), configuration.getEventListenerContext(), configuration.getOperationInvocationHandler(), - configuration.getDefaultKeyGenerator()); + configuration.getDefaultKeyGenerator(), + configuration.getDefaultKeyGenerationStrategy()); } + @Deprecated public ImmutableRepositoryFactoryConfiguration( RepositoryMetadataResolver metadataResolver, MethodQueryDescriptionExtractor queryDescriptionExtractor, @@ -53,6 +57,29 @@ public ImmutableRepositoryFactoryConfiguration( DataStoreEventListenerContext eventListenerContext, NonDataOperationInvocationHandler operationInvocationHandler, KeyGenerator keyGenerator) { + this(metadataResolver, + queryDescriptionExtractor, + functionRegistry, + dataStoreRegistry, + resultAdapterContext, + typeMappingContext, + eventListenerContext, + operationInvocationHandler, + keyGenerator, + KeyGenerationStrategy.ONLY_NULL); + } + + public ImmutableRepositoryFactoryConfiguration( + RepositoryMetadataResolver metadataResolver, + MethodQueryDescriptionExtractor queryDescriptionExtractor, + DataFunctionRegistry functionRegistry, + DataStoreRegistry dataStoreRegistry, + ResultAdapterContext resultAdapterContext, + TypeMappingContext typeMappingContext, + DataStoreEventListenerContext eventListenerContext, + NonDataOperationInvocationHandler operationInvocationHandler, + KeyGenerator keyGenerator, + KeyGenerationStrategy keyGenerationStrategy) { this.metadataResolver = metadataResolver; this.queryDescriptionExtractor = queryDescriptionExtractor; this.functionRegistry = functionRegistry; @@ -62,6 +89,7 @@ public ImmutableRepositoryFactoryConfiguration( this.eventListenerContext = eventListenerContext; this.operationInvocationHandler = operationInvocationHandler; this.keyGenerator = keyGenerator; + this.keyGenerationStrategy = keyGenerationStrategy; } @Override @@ -108,4 +136,9 @@ public NonDataOperationInvocationHandler getOperationInvocationHandler() { public KeyGenerator getDefaultKeyGenerator() { return keyGenerator; } + + @Override + public KeyGenerationStrategy getDefaultKeyGenerationStrategy() { + return keyGenerationStrategy; + } } diff --git a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/repository/CrudRepositorySupport.java b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/repository/CrudRepositorySupport.java index f6d969bd..f79b4fb1 100644 --- a/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/repository/CrudRepositorySupport.java +++ b/spring-data-mock/src/main/java/com/mmnaseri/utils/spring/data/repository/CrudRepositorySupport.java @@ -1,10 +1,6 @@ package com.mmnaseri.utils.spring.data.repository; -import com.mmnaseri.utils.spring.data.domain.DataStoreAware; -import com.mmnaseri.utils.spring.data.domain.KeyGenerator; -import com.mmnaseri.utils.spring.data.domain.KeyGeneratorAware; -import com.mmnaseri.utils.spring.data.domain.RepositoryMetadata; -import com.mmnaseri.utils.spring.data.domain.RepositoryMetadataAware; +import com.mmnaseri.utils.spring.data.domain.*; import com.mmnaseri.utils.spring.data.store.DataStore; import com.mmnaseri.utils.spring.data.tools.PropertyUtils; import org.apache.commons.logging.Log; @@ -25,6 +21,7 @@ public class CrudRepositorySupport private static final Log log = LogFactory.getLog(CrudRepositorySupport.class); private KeyGenerator keyGenerator; + private KeyGenerationStrategy keyGenerationStrategy = KeyGenerationStrategy.ONLY_NULL; private DataStore dataStore; private RepositoryMetadata repositoryMetadata; @@ -43,9 +40,9 @@ public Object save(Object entity) { } Object key = PropertyUtils.getPropertyValue(entity, repositoryMetadata.getIdentifierProperty()); log.info("The entity that is to be saved has a key with value " + key); - if (key == null && keyGenerator != null) { + if (needGenerateKey(entity, key) && keyGenerator != null) { log.info( - "The key was null, but the generator was not, so we are going to get a key for the entity"); + "The key was null or entity is not managed, but the generator was not, so we are going to get a key for the entity"); key = keyGenerator.generate(); log.debug("The generated key for the entity was " + key); PropertyUtils.setPropertyValue(entity, repositoryMetadata.getIdentifierProperty(), key); @@ -95,6 +92,11 @@ public final void setKeyGenerator(KeyGenerator keyGenerator) { this.keyGenerator = keyGenerator; } + @Override + public void setKeyGenerationStrategy(KeyGenerationStrategy strategy) { + this.keyGenerationStrategy = strategy; + } + protected DataStore getDataStore() { return dataStore; } @@ -112,4 +114,13 @@ protected RepositoryMetadata getRepositoryMetadata() { public final void setRepositoryMetadata(RepositoryMetadata repositoryMetadata) { this.repositoryMetadata = repositoryMetadata; } + + private boolean needGenerateKey(Object entity, Object key) { + return null == key || + KeyGenerationStrategy.ALL_UNMANAGED == keyGenerationStrategy && !isManaged(entity); + } + + private boolean isManaged(Object entity) { + return dataStore.retrieveAll().contains(entity); + } } diff --git a/spring-data-mock/src/test/java/com/mmnaseri/utils/spring/data/repository/DefaultCrudRepositoryWithAllUnamangedKeyGenerationStrategyTest.java b/spring-data-mock/src/test/java/com/mmnaseri/utils/spring/data/repository/DefaultCrudRepositoryWithAllUnamangedKeyGenerationStrategyTest.java new file mode 100644 index 00000000..ebfab82f --- /dev/null +++ b/spring-data-mock/src/test/java/com/mmnaseri/utils/spring/data/repository/DefaultCrudRepositoryWithAllUnamangedKeyGenerationStrategyTest.java @@ -0,0 +1,81 @@ +package com.mmnaseri.utils.spring.data.repository; + +import com.mmnaseri.utils.spring.data.domain.KeyGenerationStrategy; +import com.mmnaseri.utils.spring.data.domain.impl.ImmutableRepositoryMetadata; +import com.mmnaseri.utils.spring.data.domain.impl.key.UUIDKeyGenerator; +import com.mmnaseri.utils.spring.data.error.EntityMissingKeyException; +import com.mmnaseri.utils.spring.data.sample.models.Person; +import com.mmnaseri.utils.spring.data.sample.repositories.SimplePersonRepository; +import com.mmnaseri.utils.spring.data.store.DataStore; +import com.mmnaseri.utils.spring.data.store.impl.MemoryDataStore; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.stream.StreamSupport; + +import static com.mmnaseri.utils.spring.data.utils.TestUtils.iterableToList; +import static java.util.stream.Collectors.toList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.*; + +/** + * @author Milad Naseri (m.m.naseri@gmail.com) + * @since 1.0 (4/11/16, 10:15 AM) + */ +public class DefaultCrudRepositoryWithAllUnamangedKeyGenerationStrategyTest { + + private DefaultCrudRepository repository; + private DataStore dataStore; + + @BeforeMethod + public void setUp() { + dataStore = new MemoryDataStore<>(Person.class); + repository = new DefaultCrudRepository(); + repository.setRepositoryMetadata( + new ImmutableRepositoryMetadata(String.class, Person.class, SimplePersonRepository.class, "id")); + repository.setDataStore(dataStore); + repository.setKeyGenerator(new UUIDKeyGenerator()); + repository.setKeyGenerationStrategy(KeyGenerationStrategy.ALL_UNMANAGED); + } + + @Test + public void testSave() { + final Person managedPerson = new Person().setId("4"); + dataStore.save(managedPerson.getId(), managedPerson); + final List entities = Arrays.asList( + new Person().setId("1"), + new Person().setId("2"), + new Person().setId("3"), + managedPerson + ); + final Iterable inserted = repository.saveAll(entities); + assertThat(inserted, is(notNullValue())); + final List insertedList = StreamSupport.stream(inserted.spliterator(), /* parallel= */ false).collect(toList()); + assertThat(insertedList, hasSize(4)); + for (Object item : insertedList) { + assertThat(item, is(instanceOf(Person.class))); + assertThat((Person) item, isIn(entities)); + assertThat(((Person) item).getId(), is(notNullValue())); + if (managedPerson == item) { + assertThat(((Person) item).getId(), is("4")); + } else { + assertThat(((Person) item).getId(), not(isOneOf("1", "2", "3"))); + } + } + assertThat(dataStore.retrieveAll(), hasSize(entities.size())); + final Iterable updated = repository.saveAll(entities); + assertThat(updated, is(notNullValue())); + final List updatedList = StreamSupport.stream(updated.spliterator(), /* parallel= */ false).collect(toList()); + assertThat(updatedList, hasSize(4)); + for (Object item : updatedList) { + assertThat(item, is(instanceOf(Person.class))); + assertThat((Person) item, isIn(entities)); + } + assertThat(dataStore.retrieveAll(), hasSize(entities.size())); + } + +} diff --git a/spring-data-mock/src/test/java/com/mmnaseri/utils/spring/data/sample/usecases/proxy/InformationExposingRepositoryFactory.java b/spring-data-mock/src/test/java/com/mmnaseri/utils/spring/data/sample/usecases/proxy/InformationExposingRepositoryFactory.java index a3c4a2e1..84aca627 100644 --- a/spring-data-mock/src/test/java/com/mmnaseri/utils/spring/data/sample/usecases/proxy/InformationExposingRepositoryFactory.java +++ b/spring-data-mock/src/test/java/com/mmnaseri/utils/spring/data/sample/usecases/proxy/InformationExposingRepositoryFactory.java @@ -1,5 +1,6 @@ package com.mmnaseri.utils.spring.data.sample.usecases.proxy; +import com.mmnaseri.utils.spring.data.domain.KeyGenerationStrategy; import com.mmnaseri.utils.spring.data.domain.KeyGenerator; import com.mmnaseri.utils.spring.data.proxy.RepositoryConfiguration; import com.mmnaseri.utils.spring.data.proxy.RepositoryFactory; @@ -25,8 +26,8 @@ public InformationExposingRepositoryFactory(RepositoryFactoryConfiguration confi } @Override - public E getInstance(KeyGenerator keyGenerator, Class repositoryInterface, Class... implementations) { - final E instance = delegate.getInstance(keyGenerator, repositoryInterface, implementations); + public E getInstance(KeyGenerator keyGenerator, KeyGenerationStrategy keyGenerationStrategy, Class repositoryInterface, Class... implementations) { + final E instance = delegate.getInstance(keyGenerator, keyGenerationStrategy, repositoryInterface, implementations); //noinspection unchecked final RepositoryConfiguration configuration = new ImmutableRepositoryConfiguration( this.configuration.getRepositoryMetadataResolver().resolve(repositoryInterface), keyGenerator,