Skip to content

Commit

Permalink
Implement Key Generation Strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanlins committed Apr 18, 2021
1 parent e854772 commit a97f9cb
Show file tree
Hide file tree
Showing 14 changed files with 304 additions and 41 deletions.
4 changes: 2 additions & 2 deletions spring-data-mock/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

<groupId>com.mmnaseri.utils</groupId>
<artifactId>spring-data-mock</artifactId>
<version>2.2.0</version>
<version>2.2.1-SNAPSHOT</version>

<name>Spring Data Mock</name>
<description>A framework for mocking Spring Data repositories</description>
Expand Down Expand Up @@ -327,4 +327,4 @@
</profile>
</profiles>

</project>
</project>
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@
public interface KeyGeneratorAware<S> {

void setKeyGenerator(KeyGenerator<? extends S> keyGenerator);
void setKeyGenerationStrategy(KeyGenerationStrategy strategy);

}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -64,6 +61,7 @@ public class RepositoryFactoryBuilder
private DataStoreEventListenerContext eventListenerContext;
private NonDataOperationInvocationHandler operationInvocationHandler;
private KeyGenerator<?> defaultKeyGenerator;
private KeyGenerationStrategy defaultKeyGenerationStrategy;

private RepositoryFactoryBuilder() {
metadataResolver = new DefaultRepositoryMetadataResolver();
Expand All @@ -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
Expand Down Expand Up @@ -251,17 +250,28 @@ public RepositoryFactoryConfiguration configure() {
typeMappingContext,
eventListenerContext,
operationInvocationHandler,
defaultKeyGenerator);
defaultKeyGenerator,
defaultKeyGenerationStrategy);
}

@Override
public <S> Implementation generateKeysUsing(KeyGenerator<S> keyGenerator) {
return new RepositoryMockBuilder().useFactory(build()).generateKeysUsing(keyGenerator);
return generateKeysUsing(keyGenerator, defaultKeyGenerationStrategy);
}

@Override
public <S> Implementation generateKeysUsing(KeyGenerator<S> keyGenerator, KeyGenerationStrategy keyGenerationStrategy) {
return new RepositoryMockBuilder().useFactory(build()).generateKeysUsing(keyGenerator, keyGenerationStrategy);
}

@Override
public <S, G extends KeyGenerator<S>> Implementation generateKeysUsing(Class<G> generatorType) {
return new RepositoryMockBuilder().useFactory(build()).generateKeysUsing(generatorType);
return generateKeysUsing(generatorType, defaultKeyGenerationStrategy);
}

@Override
public <S, G extends KeyGenerator<S>> Implementation generateKeysUsing(Class<G> generatorType, KeyGenerationStrategy keyGenerationStrategy) {
return new RepositoryMockBuilder().useFactory(build()).generateKeysUsing(generatorType, keyGenerationStrategy);
}

@Override
Expand Down Expand Up @@ -291,7 +301,8 @@ public static RepositoryFactoryConfiguration defaultConfiguration() {
builder.typeMappingContext,
builder.eventListenerContext,
builder.operationInvocationHandler,
builder.defaultKeyGenerator);
builder.defaultKeyGenerator,
builder.defaultKeyGenerationStrategy);
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -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;

/**
Expand All @@ -19,6 +20,15 @@ public interface KeyGeneration extends Implementation {
*/
<S> Implementation generateKeysUsing(KeyGenerator<S> keyGenerator);

/**
* Sets the key generator to the provided instance
*
* @param keyGenerator the key generator
* @param <S> the type of the keys
* @return the rest of the configuration
*/
<S> Implementation generateKeysUsing(KeyGenerator<S> keyGenerator, KeyGenerationStrategy keyGenerationStrategy);

/**
* Sets the key generator to an instance of the provided type.
*
Expand All @@ -29,6 +39,16 @@ public interface KeyGeneration extends Implementation {
*/
<S, G extends KeyGenerator<S>> Implementation generateKeysUsing(Class<G> generatorType);

/**
* Sets the key generator to an instance of the provided type.
*
* @param generatorType the type of the key generator to use
* @param <S> the type of the keys the generator will be generating
* @param <G> the type of the generator
* @return the rest of the configuration
*/
<S, G extends KeyGenerator<S>> Implementation generateKeysUsing(Class<G> generatorType, KeyGenerationStrategy keyGenerationStrategy);

/**
* Tells the builder that we are not going to have any auto-generated keys
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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.
Expand All @@ -23,34 +25,36 @@ public class RepositoryMockBuilder implements Start, ImplementationAnd, KeyGener
private final RepositoryFactory factory;
private final List<Class<?>> 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<Class<?>> implementations, KeyGenerator<?> keyGenerator) {
RepositoryFactory factory, List<Class<?>> 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<Class<?>> implementations = new LinkedList<>(this.implementations);
implementations.add(implementation);
return new RepositoryMockBuilder(factory, implementations, keyGenerator);
return new RepositoryMockBuilder(factory, implementations, keyGenerator, keyGenerationStrategy);
}

@Override
Expand All @@ -60,19 +64,29 @@ public ImplementationAnd and(Class<?> implementation) {

@Override
public <S> Implementation generateKeysUsing(KeyGenerator<S> keyGenerator) {
return new RepositoryMockBuilder(factory, implementations, keyGenerator);
return generateKeysUsing(keyGenerator, keyGenerationStrategy);
}

@Override
public <S> Implementation generateKeysUsing(KeyGenerator<S> keyGenerator, KeyGenerationStrategy keyGenerationStrategy) {
return new RepositoryMockBuilder(factory, implementations, keyGenerator, keyGenerationStrategy);
}

@Override
public <S, G extends KeyGenerator<S>> Implementation generateKeysUsing(Class<G> generatorType) {
return generateKeysUsing(generatorType, keyGenerationStrategy);
}

@Override
public <S, G extends KeyGenerator<S>> Implementation generateKeysUsing(Class<G> 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<? extends KeyGenerator> generatorType) {
Expand Down Expand Up @@ -108,10 +122,10 @@ public <E> E mock(Class<E> 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]));
}
}
}
Original file line number Diff line number Diff line change
@@ -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;

/**
Expand All @@ -26,9 +27,30 @@ public interface RepositoryFactory {
* @param <E> 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> E getInstance(
KeyGenerator<?> keyGenerator, Class<E> 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 <E> 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> E getInstance(
KeyGenerator<?> keyGenerator, Class<E> repositoryInterface, Class... implementations);
KeyGenerator<?> keyGenerator, KeyGenerationStrategy keyGenerationStrategy, Class<E> repositoryInterface, Class... implementations);

/** @return the configuration bound to this repository factory */
RepositoryFactoryConfiguration getConfiguration();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -73,7 +67,7 @@ public DefaultRepositoryFactory(RepositoryFactoryConfiguration configuration) {

@Override
public <E> E getInstance(
KeyGenerator<?> keyGenerator, Class<E> repositoryInterface, Class... implementations) {
KeyGenerator<?> keyGenerator, KeyGenerationStrategy keyGenerationStrategy, Class<E> repositoryInterface, Class... implementations) {
final KeyGenerator<?> actualKeyGenerator;
if (keyGenerator == null) {
if (configuration.getDefaultKeyGenerator() != null) {
Expand All @@ -87,6 +81,19 @@ public <E> 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
Expand All @@ -104,7 +111,7 @@ public <E> E getInstance(
log.info(
"Trying to find all the proper type mappings for entity repository " + repositoryInterface);
final List<TypeMapping<?>> typeMappings =
getTypeMappings(metadata, dataStore, actualKeyGenerator, implementations);
getTypeMappings(metadata, dataStore, actualKeyGenerator, actualKeyGenerationStrategy, implementations);
// set up the data operation resolver
final DataOperationResolver operationResolver =
new DefaultDataOperationResolver(
Expand Down Expand Up @@ -176,13 +183,15 @@ 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
*/
private List<TypeMapping<?>> getTypeMappings(
RepositoryMetadata metadata,
DataStore<Object, ?> dataStore,
KeyGenerator<?> keyGenerator,
KeyGenerationStrategy keyGenerationStrategy,
Class[] implementations) {
final TypeMappingContext localContext = new DefaultTypeMappingContext(typeMappingContext);
for (Class implementation : implementations) {
Expand All @@ -203,6 +212,7 @@ private List<TypeMapping<?>> getTypeMappings(
KeyGeneratorAware instance = (KeyGeneratorAware) mapping.getInstance();
//noinspection unchecked
instance.setKeyGenerator(keyGenerator);
instance.setKeyGenerationStrategy(keyGenerationStrategy);
}
if (mapping.getInstance() instanceof RepositoryFactoryConfigurationAware) {
RepositoryFactoryConfigurationAware instance =
Expand Down
Loading

0 comments on commit a97f9cb

Please sign in to comment.