Skip to content

Commit 361fa82

Browse files
committed
Follow up to JAVA-2792: Allow custom results in the mapper
- parse supertypes when looking for mapped entities in custom result types - only pass Exception to MapperResultProducer.wrapError - remove <EntityT> from signature of MapperResultProducer.execute: it's rarely useful - update examples This amends d523e9a.
1 parent 529c4eb commit 361fa82

5 files changed

Lines changed: 74 additions & 39 deletions

File tree

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

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ public abstract static class ListenableFutureProducer implements MapperResultPro
4444

4545
@Nullable
4646
@Override
47-
public <EntityT> Object execute(
47+
public ListenableFuture<?> execute(
4848
@NonNull Statement<?> statement,
4949
@NonNull MapperContext context,
50-
@Nullable EntityHelper<EntityT> entityHelper) {
50+
@Nullable EntityHelper<?> entityHelper) {
5151
SettableFuture<Object> result = SettableFuture.create();
5252
context
5353
.getSession()
@@ -63,13 +63,14 @@ public <EntityT> Object execute(
6363
return result;
6464
}
6565

66-
protected abstract <EntityT> Object convert(
67-
AsyncResultSet resultSet, EntityHelper<EntityT> entityHelper);
66+
@Nullable
67+
protected abstract Object convert(
68+
@NonNull AsyncResultSet resultSet, @Nullable EntityHelper<?> entityHelper);
6869

6970
@Nullable
7071
@Override
71-
public Object wrapError(@NonNull Throwable error) {
72-
return Futures.immediateFailedFuture(error);
72+
public ListenableFuture<?> wrapError(@NonNull Exception e) {
73+
return Futures.immediateFailedFuture(e);
7374
}
7475
}
7576

@@ -83,9 +84,10 @@ public boolean canProduce(@NonNull GenericType<?> resultType) {
8384
return resultType.equals(PRODUCED_TYPE);
8485
}
8586

87+
@Nullable
8688
@Override
87-
protected <EntityT> Object convert(
88-
AsyncResultSet resultSet, EntityHelper<EntityT> entityHelper) {
89+
protected Object convert(
90+
@NonNull AsyncResultSet resultSet, @Nullable EntityHelper<?> entityHelper) {
8991
// ignore results
9092
return null;
9193
}
@@ -98,9 +100,11 @@ public boolean canProduce(@NonNull GenericType<?> resultType) {
98100
return resultType.getRawType().equals(ListenableFuture.class);
99101
}
100102

103+
@Nullable
101104
@Override
102-
protected <EntityT> Object convert(
103-
AsyncResultSet resultSet, EntityHelper<EntityT> entityHelper) {
105+
protected Object convert(
106+
@NonNull AsyncResultSet resultSet, @Nullable EntityHelper<?> entityHelper) {
107+
assert entityHelper != null;
104108
Row row = resultSet.one();
105109
return (row == null) ? null : entityHelper.get(row);
106110
}

manual/mapper/daos/custom_types/README.md

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ public class FutureOfVoidProducer implements MapperResultProducer {
5757
}
5858

5959
@Override
60-
public <EntityT> ListenableFuture<Void> execute(
61-
Statement<?> statement, MapperContext context, EntityHelper<EntityT> entityHelper) {
60+
public ListenableFuture<Void> execute(
61+
Statement<?> statement, MapperContext context, EntityHelper<?> entityHelper) {
6262
CqlSession session = context.getSession(); // (2)
6363
SettableFuture<Void> result = SettableFuture.create(); // (3)
6464
session.executeAsync(statement).whenComplete(
@@ -72,7 +72,7 @@ public class FutureOfVoidProducer implements MapperResultProducer {
7272
}
7373

7474
@Override
75-
public ListenableFuture<Void> wrapError(Throwable error) {
75+
public ListenableFuture<Void> wrapError(Exception error) {
7676
return Futures.immediateFailedFuture(error); // (4)
7777
}
7878
}
@@ -104,16 +104,17 @@ to read.
104104
#### Future of entity
105105

106106
```java
107-
public static class FutureOfEntityProducer implements MapperResultProducer {
107+
public class FutureOfEntityProducer implements MapperResultProducer {
108108
@Override
109109
public boolean canProduce(GenericType<?> resultType) {
110110
return resultType.getRawType().equals(ListenableFuture.class); // (1)
111111
}
112112

113113
@Override
114-
public <EntityT> ListenableFuture<EntityT> execute(
115-
Statement<?> statement, MapperContext context, EntityHelper<EntityT> entityHelper) {
116-
SettableFuture<EntityT> result = SettableFuture.create();
114+
public ListenableFuture<?> execute(
115+
Statement<?> statement, MapperContext context, EntityHelper<?> entityHelper) {
116+
assert entityHelper != null;
117+
SettableFuture<Object> result = SettableFuture.create();
117118
CqlSession session = context.getSession();
118119
session
119120
.executeAsync(statement)
@@ -130,7 +131,7 @@ public static class FutureOfEntityProducer implements MapperResultProducer {
130131
}
131132

132133
@Override
133-
public ListenableFuture<?> wrapError(Throwable error) {
134+
public ListenableFuture<?> wrapError(Exception error) {
134135
return Futures.immediateFailedFuture(error); // same as other producer
135136
}
136137
}
@@ -176,8 +177,6 @@ public boolean canProduce(GenericType<?> genericType) {
176177
}
177178
```
178179

179-
As you can see, this is not the most pleasant API to work with.
180-
181180
### Packaging the producers in a service
182181

183182
Once all the producers are ready, we package them in a class that implements

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,7 @@ public CodeBlock wrapWithErrorHandling(
414414
CodeBlock.builder()
415415
.beginControlFlow("try")
416416
.addStatement(
417-
"@$1T(\"unchecked\") $2T result =\n($2T) producer.wrapError(t)",
417+
"@$1T(\"unchecked\") $2T result =\n($2T) producer.wrapError(e)",
418418
SuppressWarnings.class,
419419
returnTypeName)
420420
.addStatement("return result");
@@ -423,14 +423,14 @@ public CodeBlock wrapWithErrorHandling(
423423
// (note: manually a multi-catch would be cleaner, but from here it's simpler to generate
424424
// separate clauses)
425425
for (TypeMirror thrownType : methodElement.getThrownTypes()) {
426-
callWrapError.nextControlFlow("catch ($T e)", thrownType).addStatement("throw e");
426+
callWrapError.nextControlFlow("catch ($T e2)", thrownType).addStatement("throw e2");
427427
}
428428

429429
// Otherwise, rethrow unchecked exceptions and wrap checked ones.
430430
callWrapError
431-
.nextControlFlow("catch ($T e)", Exception.class)
432-
.addStatement("$T.throwIfUnchecked(e)", Throwables.class)
433-
.addStatement("throw new $T(e)", RuntimeException.class)
431+
.nextControlFlow("catch ($T e2)", Exception.class)
432+
.addStatement("$T.throwIfUnchecked(e2)", Throwables.class)
433+
.addStatement("throw new $T(e2)", RuntimeException.class)
434434
.endControlFlow();
435435

436436
return wrapWithErrorHandling(innerBlock, callWrapError.build());
@@ -466,20 +466,20 @@ static CodeBlock wrapWithErrorHandling(CodeBlock innerBlock, CodeBlock catchBloc
466466
return CodeBlock.builder()
467467
.beginControlFlow("try")
468468
.add(innerBlock)
469-
.nextControlFlow("catch ($T t)", Throwable.class)
469+
.nextControlFlow("catch ($T e)", Exception.class)
470470
.add(catchBlock)
471471
.endControlFlow()
472472
.build();
473473
}
474474

475475
private static final CodeBlock FAILED_FUTURE =
476476
CodeBlock.builder()
477-
.addStatement("return $T.failedFuture(t)", CompletableFutures.class)
477+
.addStatement("return $T.failedFuture(e)", CompletableFutures.class)
478478
.build();
479479
private static final CodeBlock FAILED_REACTIVE_RESULT_SET =
480-
CodeBlock.builder().addStatement("return new $T(t)", FailedReactiveResultSet.class).build();
480+
CodeBlock.builder().addStatement("return new $T(e)", FailedReactiveResultSet.class).build();
481481
private static final CodeBlock FAILED_MAPPED_REACTIVE_RESULT_SET =
482482
CodeBlock.builder()
483-
.addStatement("return new $T(t)", FailedMappedReactiveResultSet.class)
483+
.addStatement("return new $T(e)", FailedMappedReactiveResultSet.class)
484484
.build();
485485
}

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

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,14 @@
2626
import com.datastax.oss.driver.internal.mapper.processor.ProcessorContext;
2727
import com.datastax.oss.driver.shaded.guava.common.collect.ImmutableMap;
2828
import edu.umd.cs.findbugs.annotations.NonNull;
29+
import java.util.ArrayList;
30+
import java.util.List;
2931
import java.util.Map;
3032
import java.util.Optional;
3133
import java.util.concurrent.CompletableFuture;
3234
import java.util.concurrent.CompletionStage;
3335
import javax.lang.model.element.Element;
36+
import javax.lang.model.element.ElementKind;
3437
import javax.lang.model.element.Name;
3538
import javax.lang.model.element.TypeElement;
3639
import javax.lang.model.type.DeclaredType;
@@ -210,7 +213,7 @@ public DaoReturnType parse(
210213
if (context.areCustomResultsEnabled()) {
211214
return new DaoReturnType(
212215
DefaultDaoReturnTypeKind.CUSTOM,
213-
findEntityInCustomType(declaredReturnType, typeParameters));
216+
findEntityInCustomType(declaredReturnType, typeParameters, new ArrayList<>()));
214217
}
215218
}
216219

@@ -246,17 +249,47 @@ public DaoReturnType parse(
246249
* appear at any level of nesting in the type, e.g. {@code MyCustomFuture<Optional<Entity>>}.
247250
*/
248251
private TypeElement findEntityInCustomType(
249-
TypeMirror typeMirror, Map<Name, TypeElement> typeParameters) {
250-
TypeElement entityElement = EntityUtils.asEntityElement(typeMirror, typeParameters);
252+
TypeMirror type,
253+
Map<Name, TypeElement> typeParameters,
254+
List<TypeMirror> alreadyCheckedTypes) {
255+
256+
// Generic types can be recursive! e.g. Integer implements Comparable<Integer>. Avoid infinite
257+
// recursion:
258+
for (TypeMirror alreadyCheckedType : alreadyCheckedTypes) {
259+
if (context.getTypeUtils().isSameType(type, alreadyCheckedType)) {
260+
return null;
261+
}
262+
}
263+
alreadyCheckedTypes.add(type);
264+
265+
TypeElement entityElement = EntityUtils.asEntityElement(type, typeParameters);
251266
if (entityElement != null) {
252267
return entityElement;
253-
} else if (typeMirror.getKind() == TypeKind.DECLARED) {
254-
for (TypeMirror typeArgument : ((DeclaredType) typeMirror).getTypeArguments()) {
255-
entityElement = findEntityInCustomType(typeArgument, typeParameters);
268+
} else if (type.getKind() == TypeKind.DECLARED) {
269+
// Check type arguments, e.g. `Foo<T>` where T = Product
270+
DeclaredType declaredType = (DeclaredType) type;
271+
for (TypeMirror typeArgument : declaredType.getTypeArguments()) {
272+
entityElement = findEntityInCustomType(typeArgument, typeParameters, alreadyCheckedTypes);
256273
if (entityElement != null) {
257274
return entityElement;
258275
}
259276
}
277+
Element element = declaredType.asElement();
278+
if (element.getKind() == ElementKind.CLASS || element.getKind() == ElementKind.INTERFACE) {
279+
// Check interfaces, e.g. `Foo implements Iterable<T>`, where T = Product
280+
TypeElement typeElement = (TypeElement) element;
281+
for (TypeMirror parentInterface : typeElement.getInterfaces()) {
282+
entityElement =
283+
findEntityInCustomType(parentInterface, typeParameters, alreadyCheckedTypes);
284+
if (entityElement != null) {
285+
return entityElement;
286+
}
287+
}
288+
// Check superclass (if there is none then the mirror has TypeKind.NONE and the recursive
289+
// call will return null).
290+
return findEntityInCustomType(
291+
typeElement.getSuperclass(), typeParameters, alreadyCheckedTypes);
292+
}
260293
}
261294
// null is a valid result even at the top level, a custom type may not contain any entity
262295
return null;

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,11 @@ public interface MapperResultProducer {
8484
* @return the object to return from the DAO method. This must match the type that this producer
8585
* was selected for, there will be an unchecked cast at runtime.
8686
*/
87-
@SuppressWarnings("TypeParameterUnusedInFormals")
8887
@Nullable
89-
<EntityT> Object execute(
88+
Object execute(
9089
@NonNull Statement<?> statement,
9190
@NonNull MapperContext context,
92-
@Nullable EntityHelper<EntityT> entityHelper);
91+
@Nullable EntityHelper<?> entityHelper);
9392

9493
/**
9594
* Surfaces any error encountered in the DAO method (either in the generated mapper code that
@@ -103,5 +102,5 @@ <EntityT> Object execute(
103102
* declares them, or wrapped into a {@link RuntimeException} otherwise.
104103
*/
105104
@Nullable
106-
Object wrapError(@NonNull Throwable error) throws Exception;
105+
Object wrapError(@NonNull Exception e) throws Exception;
107106
}

0 commit comments

Comments
 (0)