Skip to content

Commit 9d6f050

Browse files
GH-2544 - Check for empty collections in saveAllAs methods.
When an empty collection is passed to imperative and reactive `saveAllAs` variants a common element type can't be computed. We did not check for this scenario correctly and failed with a NPE. The introduction of a marker class fixes #2544 Co-authored-by: Michael Simons <[email protected]>
1 parent 4938e6a commit 9d6f050

File tree

6 files changed

+73
-1
lines changed

6 files changed

+73
-1
lines changed

src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
* @author Michael J. Simons
9999
* @author Philipp Tölle
100100
* @author Gerrit Meier
101+
* @author Corey Beres
101102
* @soundtrack Motörhead - We Are Motörhead
102103
* @since 6.0
103104
*/
@@ -549,6 +550,14 @@ public <T, R> List<R> saveAllAs(Iterable<T> instances, Class<R> resultType) {
549550

550551
Class<?> commonElementType = TemplateSupport.findCommonElementType(instances);
551552

553+
if (commonElementType == null) {
554+
throw new IllegalArgumentException("Could not determine a common element of an heterogeneous collection.");
555+
}
556+
557+
if (commonElementType == TemplateSupport.EmptyIterable.class) {
558+
return Collections.emptyList();
559+
}
560+
552561
if (resultType.isAssignableFrom(commonElementType)) {
553562
@SuppressWarnings("unchecked") // Nicer to live with this than streaming, mapping and collecting to avoid the cast. It's easier on the reactive side.
554563
List<R> saveElements = (List<R>) saveAll(instances);

src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,15 @@ public <T, R> Flux<R> saveAllAs(Iterable<T> instances, Class<R> resultType) {
491491

492492
Class<?> commonElementType = TemplateSupport.findCommonElementType(instances);
493493

494+
if (commonElementType == null) {
495+
return Flux.error(() -> new IllegalArgumentException(
496+
"Could not determine a common element of an heterogeneous collection."));
497+
}
498+
499+
if (commonElementType == TemplateSupport.EmptyIterable.class) {
500+
return Flux.empty();
501+
}
502+
494503
if (resultType.isAssignableFrom(commonElementType)) {
495504
return saveAll(instances).map(resultType::cast);
496505
}

src/main/java/org/springframework/data/neo4j/core/TemplateSupport.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@
6464
@API(status = API.Status.INTERNAL, since = "6.0.9")
6565
public final class TemplateSupport {
6666

67+
/**
68+
* Indicator for an empty collection
69+
*/
70+
public static final class EmptyIterable {
71+
private EmptyIterable() {
72+
}
73+
}
74+
6775
enum FetchType {
6876

6977
ONE,
@@ -81,6 +89,10 @@ public static Class<?> findCommonElementType(Iterable<?> collection) {
8189
.filter(o -> o != null)
8290
.map(Object::getClass).collect(Collectors.toSet());
8391

92+
if (allClasses.isEmpty()) {
93+
return EmptyIterable.class;
94+
}
95+
8496
Class<?> candidate = null;
8597
for (Class<?> type : allClasses) {
8698
if (candidate == null) {

src/test/java/org/springframework/data/neo4j/core/TemplateSupportTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ void shouldNotFailWithNullInput() {
8888
void shouldNotFailWithEmptyInput() {
8989

9090
Class<?> type = TemplateSupport.findCommonElementType(Collections.emptyList());
91-
assertThat(type).isNull();
91+
assertThat(type).isEqualTo(TemplateSupport.EmptyIterable.class);
9292
}
9393

9494
@Test

src/test/java/org/springframework/data/neo4j/integration/imperative/Neo4jTemplateIT.java

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

1818
import static org.assertj.core.api.Assertions.assertThat;
1919
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
20+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
2021

2122
import lombok.Data;
2223

24+
import java.util.ArrayList;
2325
import java.util.Arrays;
2426
import java.util.Collection;
2527
import java.util.Collections;
@@ -76,6 +78,7 @@
7678
* @author Gerrit Meier
7779
* @author Michael J. Simons
7880
* @author Rosetta Roberts
81+
* @author Corey Beres
7982
*/
8083
@Neo4jIntegrationTest
8184
class Neo4jTemplateIT {
@@ -795,6 +798,24 @@ void saveAllAsWithClosedProjectionShouldWork() {
795798
assertThat(people).allMatch(p -> p.getAddress() != null);
796799
}
797800

801+
@Test // GH-2544
802+
void saveAllAsWithEmptyList() {
803+
List<ClosedProjection> projections = neo4jTemplate.saveAllAs(Collections.emptyList(), ClosedProjection.class);
804+
805+
assertThat(projections).isEmpty();
806+
}
807+
808+
@Test // GH-2544
809+
void saveWeirdHierarchy() {
810+
811+
List<Object> things = new ArrayList<>();
812+
things.add(1);
813+
things.add("eins");
814+
815+
assertThatIllegalArgumentException().isThrownBy(() -> neo4jTemplate.saveAllAs(things, ClosedProjection.class))
816+
.withMessage("Could not determine a common element of an heterogeneous collection.");
817+
}
818+
798819
@Test
799820
void updatingFindShouldWork() {
800821
Map<String, Object> params = new HashMap<>();

src/test/java/org/springframework/data/neo4j/integration/reactive/ReactiveNeo4jTemplateIT.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import reactor.core.publisher.Flux;
2323
import reactor.test.StepVerifier;
2424

25+
import java.util.ArrayList;
2526
import java.util.Arrays;
2627
import java.util.Collection;
2728
import java.util.Collections;
@@ -688,6 +689,26 @@ void saveAsWithClosedProjectionOnThreeLevelShouldWork(@Autowired ReactiveNeo4jTe
688689
.verifyComplete();
689690
}
690691

692+
@Test // GH-2544
693+
void saveAllAsWithEmptyList(@Autowired ReactiveNeo4jTemplate template) {
694+
695+
template.saveAllAs(Collections.emptyList(), ClosedProjection.class)
696+
.as(StepVerifier::create)
697+
.verifyComplete();
698+
}
699+
700+
@Test // GH-2544
701+
void saveWeirdHierarchy(@Autowired ReactiveNeo4jTemplate template) {
702+
703+
List<Object> things = new ArrayList<>();
704+
things.add(1);
705+
things.add("eins");
706+
707+
template.saveAllAs(things, ClosedProjection.class)
708+
.as(StepVerifier::create)
709+
.verifyErrorMatches(t -> t instanceof IllegalArgumentException && t.getMessage().equals("Could not determine a common element of an heterogeneous collection."));
710+
}
711+
691712
@Test
692713
void saveAllAsWithClosedProjectionShouldWork(@Autowired ReactiveNeo4jTemplate template) {
693714

0 commit comments

Comments
 (0)