Skip to content

Commit bc40944

Browse files
committed
JAVA-2262: Allow user to store custom state in MapperContext
1 parent 38cb3f3 commit bc40944

6 files changed

Lines changed: 77 additions & 6 deletions

File tree

integration-tests/src/test/java/com/datastax/oss/driver/mapper/QueryProviderIT.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import com.datastax.oss.driver.api.testinfra.session.SessionRule;
4242
import com.datastax.oss.driver.categories.ParallelizableTests;
4343
import java.util.Objects;
44+
import java.util.concurrent.atomic.AtomicInteger;
4445
import org.junit.BeforeClass;
4546
import org.junit.ClassRule;
4647
import org.junit.Test;
@@ -56,6 +57,10 @@ public class QueryProviderIT {
5657
private static SessionRule<CqlSession> sessionRule = SessionRule.builder(ccm).build();
5758
@ClassRule public static TestRule chain = RuleChain.outerRule(ccm).around(sessionRule);
5859

60+
// Dummy counter to exercize the "custom state" feature: it gets incremented each time the query
61+
// provider is called.
62+
private static AtomicInteger executionCount = new AtomicInteger();
63+
5964
private static SensorDao dao;
6065

6166
@BeforeClass
@@ -70,7 +75,10 @@ public static void setup() {
7075
.setExecutionProfile(sessionRule.slowProfile())
7176
.build());
7277

73-
SensorMapper mapper = new QueryProviderIT_SensorMapperBuilder(session).build();
78+
SensorMapper mapper =
79+
new QueryProviderIT_SensorMapperBuilder(session)
80+
.withCustomState("executionCount", executionCount)
81+
.build();
7482
dao = mapper.sensorDao(sessionRule.keyspace());
7583
}
7684

@@ -85,11 +93,15 @@ public void should_invoke_query_provider() {
8593
dao.save(readingFeb1);
8694
dao.save(readingJan31);
8795

96+
assertThat(executionCount.get()).isEqualTo(0);
97+
8898
assertThat(dao.findSlice(1, null, null).all())
8999
.containsExactly(readingFeb3, readingFeb2, readingFeb1, readingJan31);
90100
assertThat(dao.findSlice(1, 2, null).all())
91101
.containsExactly(readingFeb3, readingFeb2, readingFeb1);
92102
assertThat(dao.findSlice(1, 2, 3).all()).containsExactly(readingFeb3);
103+
104+
assertThat(executionCount.get()).isEqualTo(3);
93105
}
94106

95107
@Mapper
@@ -109,12 +121,14 @@ public interface SensorDao {
109121

110122
public static class FindSliceProvider {
111123
private final CqlSession session;
124+
private final AtomicInteger executionCount;
112125
private final EntityHelper<SensorReading> sensorReadingHelper;
113126
private final Select selectStart;
114127

115128
public FindSliceProvider(
116129
MapperContext context, EntityHelper<SensorReading> sensorReadingHelper) {
117130
this.session = context.getSession();
131+
this.executionCount = ((AtomicInteger) context.getCustomState().get("executionCount"));
118132
this.sensorReadingHelper = sensorReadingHelper;
119133
this.selectStart =
120134
sensorReadingHelper.selectStart().whereColumn("id").isEqualTo(bindMarker());
@@ -125,6 +139,8 @@ public PagingIterable<SensorReading> findSlice(int id, Integer month, Integer da
125139
throw new IllegalArgumentException("Can't specify day if month is null");
126140
}
127141

142+
executionCount.incrementAndGet();
143+
128144
Select select = this.selectStart;
129145
if (month != null) {
130146
select = select.whereColumn("month").isEqualTo(bindMarker());

mapper-processor/src/main/java/com/datastax/oss/driver/internal/mapper/processor/mapper/MapperBuilderGenerator.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ protected JavaFile.Builder getContents() {
7070
.addModifiers(Modifier.PUBLIC)
7171
.addAnnotation(Override.class)
7272
.returns(ClassName.get(interfaceElement))
73-
.addStatement("$1T context = new $1T(session)", DefaultMapperContext.class)
73+
.addStatement(
74+
"$1T context = new $1T(session, customState)", DefaultMapperContext.class)
7475
.addStatement(
7576
"return new $T(context)",
7677
GeneratedNames.mapperImplementation(interfaceElement))

mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/MapperBuilder.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717

1818
import com.datastax.oss.driver.api.core.CqlSession;
1919
import com.datastax.oss.driver.api.mapper.annotations.Mapper;
20+
import com.datastax.oss.driver.api.mapper.annotations.QueryProvider;
21+
import edu.umd.cs.findbugs.annotations.NonNull;
22+
import edu.umd.cs.findbugs.annotations.Nullable;
23+
import java.util.HashMap;
24+
import java.util.Map;
2025

2126
/**
2227
* Builds an instance of a {@link Mapper}-annotated interface wrapping a {@link CqlSession}.
@@ -27,9 +32,27 @@
2732
public abstract class MapperBuilder<MapperT> {
2833

2934
protected final CqlSession session;
35+
protected Map<Object, Object> customState;
3036

3137
protected MapperBuilder(CqlSession session) {
3238
this.session = session;
39+
this.customState = new HashMap<>();
40+
}
41+
42+
/**
43+
* Stores custom state that will be propagated to {@link MapperContext#getCustomState()}.
44+
*
45+
* <p>This is intended mainly for {@link QueryProvider} methods: since provider classes are
46+
* instantiated directly by the generated mapper code, they have no way to access non-static state
47+
* from the rest of your application. This method allows you to pass that state while building the
48+
* mapper, and access it later at runtime.
49+
*
50+
* <p>Note that this state will be accessed concurrently, it should be thread-safe.
51+
*/
52+
@NonNull
53+
public MapperBuilder<MapperT> withCustomState(@Nullable Object key, @Nullable Object value) {
54+
customState.put(key, value);
55+
return this;
3356
}
3457

3558
public abstract MapperT build();

mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/MapperContext.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.datastax.oss.driver.api.mapper.entity.naming.NameConverter;
2121
import edu.umd.cs.findbugs.annotations.NonNull;
2222
import edu.umd.cs.findbugs.annotations.Nullable;
23+
import java.util.Map;
2324

2425
/**
2526
* A runtime context that gets passed from the mapper to DAO components to share global resources
@@ -52,4 +53,13 @@ public interface MapperContext {
5253
*/
5354
@NonNull
5455
NameConverter getNameConverter(Class<? extends NameConverter> converterClass);
56+
57+
/**
58+
* Retrieves any custom state that was set while building the mapper with {@link
59+
* MapperBuilder#withCustomState(Object, Object)}.
60+
*
61+
* <p>The returned map is immutable. If no state was set on the builder, it will be empty.
62+
*/
63+
@NonNull
64+
Map<Object, Object> getCustomState();
5565
}

mapper-runtime/src/main/java/com/datastax/oss/driver/api/mapper/annotations/QueryProvider.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.datastax.oss.driver.api.mapper.annotations;
1717

18+
import com.datastax.oss.driver.api.mapper.MapperBuilder;
1819
import com.datastax.oss.driver.api.mapper.MapperContext;
1920
import com.datastax.oss.driver.api.mapper.entity.EntityHelper;
2021
import java.lang.annotation.ElementType;
@@ -52,6 +53,8 @@
5253
*
5354
* <p>The parameters and return type are completely free-form, as long as they match those of the
5455
* provider method.
56+
*
57+
* @see MapperBuilder#withCustomState(Object, Object)
5558
*/
5659
@Target(ElementType.METHOD)
5760
@Retention(RetentionPolicy.CLASS)

mapper-runtime/src/main/java/com/datastax/oss/driver/internal/mapper/DefaultMapperContext.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
1919
import com.datastax.oss.driver.api.core.CqlSession;
2020
import com.datastax.oss.driver.api.mapper.MapperContext;
2121
import com.datastax.oss.driver.api.mapper.entity.naming.NameConverter;
22+
import com.datastax.oss.protocol.internal.util.collection.NullAllowingImmutableMap;
2223
import edu.umd.cs.findbugs.annotations.NonNull;
2324
import edu.umd.cs.findbugs.annotations.Nullable;
2425
import java.lang.reflect.InvocationTargetException;
26+
import java.util.Map;
2527
import java.util.Objects;
2628
import java.util.concurrent.ConcurrentHashMap;
2729
import java.util.concurrent.ConcurrentMap;
@@ -32,28 +34,38 @@ public class DefaultMapperContext implements MapperContext {
3234
private final CqlIdentifier keyspaceId;
3335
private final CqlIdentifier tableId;
3436
private final ConcurrentMap<Class<? extends NameConverter>, NameConverter> nameConverterCache;
37+
private final Map<Object, Object> customState;
3538

36-
public DefaultMapperContext(@NonNull CqlSession session) {
37-
this(session, null, null, new ConcurrentHashMap<>());
39+
public DefaultMapperContext(
40+
@NonNull CqlSession session, @NonNull Map<Object, Object> customState) {
41+
this(
42+
session,
43+
null,
44+
null,
45+
new ConcurrentHashMap<>(),
46+
NullAllowingImmutableMap.copyOf(customState));
3847
}
3948

4049
private DefaultMapperContext(
4150
CqlSession session,
4251
CqlIdentifier keyspaceId,
4352
CqlIdentifier tableId,
44-
ConcurrentMap<Class<? extends NameConverter>, NameConverter> nameConverterCache) {
53+
ConcurrentMap<Class<? extends NameConverter>, NameConverter> nameConverterCache,
54+
Map<Object, Object> customState) {
4555
this.session = session;
4656
this.keyspaceId = keyspaceId;
4757
this.tableId = tableId;
4858
this.nameConverterCache = nameConverterCache;
59+
this.customState = customState;
4960
}
5061

5162
public DefaultMapperContext withKeyspaceAndTable(
5263
@Nullable CqlIdentifier newKeyspaceId, @Nullable CqlIdentifier newTableId) {
5364
return (Objects.equals(newKeyspaceId, this.keyspaceId)
5465
&& Objects.equals(newTableId, this.tableId))
5566
? this
56-
: new DefaultMapperContext(session, newKeyspaceId, newTableId, nameConverterCache);
67+
: new DefaultMapperContext(
68+
session, newKeyspaceId, newTableId, nameConverterCache, customState);
5769
}
5870

5971
@NonNull
@@ -81,6 +93,12 @@ public NameConverter getNameConverter(Class<? extends NameConverter> converterCl
8193
converterClass, DefaultMapperContext::buildNameConverter);
8294
}
8395

96+
@NonNull
97+
@Override
98+
public Map<Object, Object> getCustomState() {
99+
return customState;
100+
}
101+
84102
private static NameConverter buildNameConverter(Class<? extends NameConverter> converterClass) {
85103
try {
86104
return converterClass.getDeclaredConstructor().newInstance();

0 commit comments

Comments
 (0)