Skip to content

Commit a383788

Browse files
committed
Refactored to be more explicit on the class instance mapping
1 parent cccb52f commit a383788

File tree

4 files changed

+113
-166
lines changed

4 files changed

+113
-166
lines changed

database-test/src/main/java/com/codeheadsystems/test/datastore/ClassInstanceManager.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ public class ClassInstanceManager {
77

88
private final HashMap<Class<?>, Object> instances = new HashMap<>();
99

10-
public <T> Optional<T> getInstance(final Class<T> clazz) {
10+
public <T> Optional<T> get(final Class<T> clazz) {
1111
return optionalIfSet(clazz, instances.get(clazz));
1212
}
1313

14-
public <T> Optional<T> removeInstance(final Class<T> clazz) {
14+
public <T> Optional<T> remove(final Class<T> clazz) {
1515
return optionalIfSet(clazz, instances.remove(clazz));
1616
}
1717

@@ -23,10 +23,14 @@ private <T> Optional<T> optionalIfSet(final Class<T> clazz, final Object instanc
2323
}
2424
}
2525

26-
public <T> void setInstance(final Class<T> clazz, final T instance) {
26+
public <T> void put(final Class<T> clazz, final T instance) {
2727
instances.put(clazz, instance);
2828
}
2929

30+
public <T> void put(final T instance) {
31+
instances.put(instance.getClass(), instance);
32+
}
33+
3034
public void clear() {
3135
instances.clear();
3236
}

database-test/src/main/java/com/codeheadsystems/test/datastore/DataStoreExtension.java

Lines changed: 0 additions & 122 deletions
This file was deleted.

database-test/src/main/java/com/codeheadsystems/test/datastore/DynamoDbExtension.java

Lines changed: 82 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@
2626
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper;
2727
import com.amazonaws.services.dynamodbv2.local.main.ServerRunner;
2828
import com.amazonaws.services.dynamodbv2.local.server.DynamoDBProxyServer;
29+
import java.lang.reflect.Field;
2930
import java.net.URI;
3031
import java.net.URISyntaxException;
31-
import java.util.Set;
32+
import java.util.Arrays;
3233
import org.junit.jupiter.api.extension.AfterAllCallback;
3334
import org.junit.jupiter.api.extension.BeforeAllCallback;
35+
import org.junit.jupiter.api.extension.BeforeEachCallback;
3436
import org.junit.jupiter.api.extension.ExtensionContext;
3537
import org.junit.jupiter.api.extension.ParameterContext;
3638
import org.junit.jupiter.api.extension.ParameterResolutionException;
@@ -47,65 +49,75 @@
4749
/**
4850
* Setups the ddb instance.
4951
*/
50-
public class DynamoDbExtension
51-
extends DataStoreExtension
52-
implements BeforeAllCallback, AfterAllCallback, ParameterResolver {
52+
public class DynamoDbExtension implements BeforeAllCallback, AfterAllCallback, BeforeEachCallback, ParameterResolver {
5353

5454
private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbExtension.class);
55-
56-
@Override
57-
protected Class<?> namespaceClass() {
58-
return DynamoDbExtension.class;
59-
}
60-
55+
private static final ExtensionContext.Namespace namespace = ExtensionContext.Namespace.create(DynamoDbExtension.class);
6156

6257
private String randomPort() {
6358
return String.valueOf((int) (Math.random() * 10000 + 1000));
6459
}
6560

61+
private ClassInstanceManager classInstanceManager(final ExtensionContext context) {
62+
return context.getStore(namespace)
63+
.getOrComputeIfAbsent(ClassInstanceManager.class,
64+
k -> new ClassInstanceManager(),
65+
ClassInstanceManager.class);
66+
}
67+
6668
@Override
6769
public void beforeAll(final ExtensionContext context) throws Exception {
6870
LOGGER.info("Setting in memory DynamoDB local instance");
69-
String port = randomPort();
70-
DynamoDBProxyServer server = ServerRunner.createServerFromCommandLineArgs(
71+
final ClassInstanceManager classInstanceManager = classInstanceManager(context);
72+
final String port = randomPort();
73+
final DynamoDBProxyServer server = ServerRunner.createServerFromCommandLineArgs(
7174
new String[]{"-inMemory", "-port", port});
7275
server.start();
73-
withStore(context, s -> {
74-
AmazonDynamoDB client = getAmazonDynamoDb(port);
75-
s.put(DynamoDBProxyServer.class, server);
76-
s.put(AmazonDynamoDB.class, client);
77-
s.put(DynamoDbClient.class, getDynamoDbClient(port));
78-
s.put(DynamoDBMapper.class, new DynamoDBMapper(client));
79-
});
76+
AmazonDynamoDB client = getAmazonDynamoDb(port);
77+
classInstanceManager.put(DynamoDBProxyServer.class, server);
78+
classInstanceManager.put(AmazonDynamoDB.class, client);
79+
classInstanceManager.put(DynamoDbClient.class, getDynamoDbClient(port));
80+
classInstanceManager.put(DynamoDBMapper.class, new DynamoDBMapper(client));
8081
}
8182

8283
@Override
8384
public void afterAll(final ExtensionContext context) {
8485
LOGGER.info("Tearing down in memory DynamoDB local instance");
85-
withStore(context, s -> {
86+
final ClassInstanceManager classInstanceManager = context.getStore(namespace).remove(ClassInstanceManager.class, ClassInstanceManager.class);
87+
if (classInstanceManager == null) {
88+
LOGGER.error("No class instance manager found");
89+
return;
90+
}
91+
classInstanceManager.remove(DynamoDBProxyServer.class).ifPresent(o -> {
8692
try {
87-
s.remove(DynamoDBProxyServer.class, DynamoDBProxyServer.class).stop();
93+
o.stop();
8894
} catch (Exception e) {
89-
LOGGER.error("Failed to tear down DynamoDBProxyServer", e);
95+
LOGGER.error("Error stopping DynamoDB proxy server", e);
9096
}
91-
s.remove(DynamoDBMapper.class);
92-
s.remove(AmazonDynamoDB.class);
93-
s.remove(DynamoDbClient.class, DynamoDbClient.class).close();
9497
});
98+
classInstanceManager.remove(DynamoDBMapper.class);
99+
classInstanceManager.remove(AmazonDynamoDB.class);
100+
classInstanceManager.remove(DynamoDbClient.class).ifPresent(DynamoDbClient::close);
101+
classInstanceManager.clear();
95102
}
96103

97104
@Override
98105
public boolean supportsParameter(final ParameterContext parameterContext,
99106
final ExtensionContext extensionContext) throws ParameterResolutionException {
100107
final Class<?> type = parameterContext.getParameter().getType();
101-
return parameterContext.isAnnotated(DataStore.class) && extensionContext.getStore(namespace).get(type) != null;
108+
final ClassInstanceManager classInstanceManager = classInstanceManager(extensionContext);
109+
return parameterContext.isAnnotated(DataStore.class) && classInstanceManager.hasInstance(type);
102110
}
103111

104112
@Override
105113
public Object resolveParameter(final ParameterContext parameterContext,
106114
final ExtensionContext extensionContext) throws ParameterResolutionException {
107115
final Class<?> type = parameterContext.getParameter().getType();
108-
return extensionContext.getStore(namespace).get(type);
116+
final ClassInstanceManager classInstanceManager = classInstanceManager(extensionContext);
117+
return classInstanceManager.get(type).orElseGet(() -> {
118+
LOGGER.error("No instance found for type {}", type.getSimpleName());
119+
return null;
120+
});
109121
}
110122

111123
private AmazonDynamoDB getAmazonDynamoDb(String port) {
@@ -136,4 +148,47 @@ private DynamoDbClient getDynamoDbClient(final String port) {
136148
}
137149
}
138150

151+
@Override
152+
public void beforeEach(final ExtensionContext context) {
153+
final ClassInstanceManager classInstanceManager = classInstanceManager(context);
154+
context.getRequiredTestInstances().getAllInstances().forEach(instance -> {
155+
Arrays.stream(instance.getClass().getDeclaredFields())
156+
.filter(f -> f.isAnnotationPresent(DataStore.class))
157+
.forEach(field -> {
158+
setValueForField(classInstanceManager, instance, field);
159+
});
160+
});
161+
}
162+
163+
private void setValueForField(final ClassInstanceManager classInstanceManager,
164+
final Object o,
165+
final Field field) {
166+
final Object value = classInstanceManager.get(field.getType())
167+
.orElseThrow(() -> new IllegalArgumentException("Unable to find DynamoDB extension value of type " + field.getType())); // Check the store to see we have this type.
168+
LOGGER.info("Setting field {}:{}", field.getName(), field.getType().getSimpleName());
169+
enableSettingTheField(field);
170+
try {
171+
field.set(o, value);
172+
} catch (IllegalAccessException e) {
173+
LOGGER.error("Unable to set the field value for {}", field.getName(), e);
174+
LOGGER.error("Continuing, but expect nothing good will happen next.");
175+
}
176+
}
177+
178+
/**
179+
* This allows us to set the field directly. It will fail if the security manager in play disallows it.
180+
* We can talk about justifications all we want, but really we know Java is not Smalltalk. Meta programming
181+
* is limited here. So... we try to do the right thing.
182+
*
183+
* @param field to change accessibility for.
184+
*/
185+
protected void enableSettingTheField(final Field field) {
186+
try {
187+
field.setAccessible(true);
188+
} catch (RuntimeException re) {
189+
LOGGER.error("Unable to change accessibility for field due to private var or security manager: {}",
190+
field.getName());
191+
LOGGER.error("The setting will likely fail. Consider changing that type to protected.", re);
192+
}
193+
}
139194
}

database-test/src/test/java/com/codeheadsystems/test/datastore/ClassInstanceManagerTest.java

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,29 @@ public void setUp() {
1616
}
1717

1818
@Test
19-
public void testSetInstance() {
19+
public void testPut() {
2020
String instance = "testInstance";
21-
classInstanceManager.setInstance(String.class, instance);
22-
assertThat(classInstanceManager.getInstance(String.class))
21+
classInstanceManager.put(String.class, instance);
22+
assertThat(classInstanceManager.get(String.class))
2323
.isPresent()
2424
.contains(instance);
2525
}
2626

2727
@Test
28-
public void testGetInstance() {
28+
public void testPut_withoutClass() {
2929
String instance = "testInstance";
30-
classInstanceManager.setInstance(String.class, instance);
31-
Optional<String> retrievedInstance = classInstanceManager.getInstance(String.class);
30+
classInstanceManager.put(instance);
31+
assertThat(classInstanceManager.get(String.class))
32+
.isPresent()
33+
.contains(instance);
34+
}
35+
36+
37+
@Test
38+
public void testGet() {
39+
String instance = "testInstance";
40+
classInstanceManager.put(String.class, instance);
41+
Optional<String> retrievedInstance = classInstanceManager.get(String.class);
3242
assertThat(retrievedInstance)
3343
.isPresent().
3444
contains(instance);
@@ -37,26 +47,26 @@ public void testGetInstance() {
3747
@Test
3848
public void testRemoveInstance() {
3949
String instance = "testInstance";
40-
classInstanceManager.setInstance(String.class, instance);
41-
Optional<String> removedInstance = classInstanceManager.removeInstance(String.class);
50+
classInstanceManager.put(String.class, instance);
51+
Optional<String> removedInstance = classInstanceManager.remove(String.class);
4252
assertThat(removedInstance)
4353
.isPresent()
4454
.contains(instance);
45-
assertThat(classInstanceManager.getInstance(String.class)).isNotPresent();
55+
assertThat(classInstanceManager.get(String.class)).isNotPresent();
4656
}
4757

4858
@Test
4959
public void testClear() {
50-
classInstanceManager.setInstance(String.class, "testInstance");
51-
classInstanceManager.setInstance(Integer.class, 123);
60+
classInstanceManager.put(String.class, "testInstance");
61+
classInstanceManager.put(Integer.class, 123);
5262
classInstanceManager.clear();
53-
assertThat(classInstanceManager.getInstance(String.class)).isNotPresent();
54-
assertThat(classInstanceManager.getInstance(Integer.class)).isNotPresent();
63+
assertThat(classInstanceManager.get(String.class)).isNotPresent();
64+
assertThat(classInstanceManager.get(Integer.class)).isNotPresent();
5565
}
5666

5767
@Test
5868
public void testHasInstance() {
59-
classInstanceManager.setInstance(String.class, "testInstance");
69+
classInstanceManager.put(String.class, "testInstance");
6070
assertThat(classInstanceManager.hasInstance(String.class)).isTrue();
6171
assertThat(classInstanceManager.hasInstance(Integer.class)).isFalse();
6272
}

0 commit comments

Comments
 (0)