Skip to content

Commit 08e76d3

Browse files
authored
GH-2762: Fix Topic Check with Consumer Overrides
* GH-2762: Fix Topic Check with Consumer Overrides Resolves #2762 When checking for missing topics, the admin was created with the raw consumer factory properties and did not apply any overriding consumer properties in the `ContainerProperties`. Apply the overrides before creating the admin. Pull up a method that merges default properties (if any) because we have to iterate over the hash table because the user might have used `put()` instead of `setProperty()` to set properties. **cherry-pick to 2.9.x** * Fix unrelated test. * Fix test class name. * Fix test class name.
1 parent f76bc09 commit 08e76d3

File tree

4 files changed

+139
-26
lines changed

4 files changed

+139
-26
lines changed

Diff for: spring-kafka/src/main/java/org/springframework/kafka/listener/AbstractMessageListenerContainer.java

+26
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.List;
2222
import java.util.Map;
2323
import java.util.Map.Entry;
24+
import java.util.Properties;
2425
import java.util.Set;
2526
import java.util.concurrent.ConcurrentHashMap;
2627
import java.util.concurrent.CountDownLatch;
@@ -565,6 +566,12 @@ protected void checkTopics() {
565566
.stream()
566567
.filter(entry -> AdminClientConfig.configNames().contains(entry.getKey()))
567568
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
569+
Properties overrides = propertiesFromConsumerPropertyOverrides();
570+
overrides.forEach((key, value) -> {
571+
if (key instanceof String) {
572+
configs.put((String) key, value);
573+
}
574+
});
568575
List<String> missing = null;
569576
try (AdminClient client = AdminClient.create(configs)) { // NOSONAR - false positive null check
570577
if (client != null) {
@@ -740,4 +747,23 @@ protected void publishContainerStoppedEvent() {
740747
return this;
741748
}
742749

750+
/**
751+
* Make any default consumer override properties explicit properties.
752+
* @return the properties.
753+
* @since 2.9.11
754+
*/
755+
protected Properties propertiesFromConsumerPropertyOverrides() {
756+
Properties propertyOverrides = this.containerProperties.getKafkaConsumerProperties();
757+
Properties props = new Properties();
758+
props.putAll(propertyOverrides);
759+
Set<String> stringPropertyNames = propertyOverrides.stringPropertyNames();
760+
// User might have provided properties as defaults
761+
stringPropertyNames.forEach((name) -> {
762+
if (!props.contains(name)) {
763+
props.setProperty(name, propertyOverrides.getProperty(name));
764+
}
765+
});
766+
return props;
767+
}
768+
743769
}

Diff for: spring-kafka/src/main/java/org/springframework/kafka/listener/KafkaMessageListenerContainer.java

+1-15
Original file line numberDiff line numberDiff line change
@@ -858,7 +858,7 @@ private final class ListenerConsumer implements SchedulingAwareRunnable, Consume
858858
ObservationRegistry observationRegistry) {
859859

860860
this.observationRegistry = observationRegistry;
861-
Properties consumerProperties = propertiesFromProperties();
861+
Properties consumerProperties = propertiesFromConsumerPropertyOverrides();
862862
checkGroupInstance(consumerProperties, KafkaMessageListenerContainer.this.consumerFactory);
863863
this.autoCommit = determineAutoCommit(consumerProperties);
864864
this.consumer =
@@ -1048,20 +1048,6 @@ private CommonErrorHandler determineCommonErrorHandler() {
10481048
}
10491049
}
10501050

1051-
private Properties propertiesFromProperties() {
1052-
Properties propertyOverrides = this.containerProperties.getKafkaConsumerProperties();
1053-
Properties props = new Properties();
1054-
props.putAll(propertyOverrides);
1055-
Set<String> stringPropertyNames = propertyOverrides.stringPropertyNames();
1056-
// User might have provided properties as defaults
1057-
stringPropertyNames.forEach((name) -> {
1058-
if (!props.contains(name)) {
1059-
props.setProperty(name, propertyOverrides.getProperty(name));
1060-
}
1061-
});
1062-
return props;
1063-
}
1064-
10651051
String getClientId() {
10661052
return this.clientId;
10671053
}

Diff for: spring-kafka/src/test/java/org/springframework/kafka/annotation/BatchListenerConversionTests.java

+11-11
Original file line numberDiff line numberDiff line change
@@ -254,17 +254,17 @@ public static class Listener {
254254

255255
private final String topic;
256256

257-
private final CountDownLatch latch1 = new CountDownLatch(1);
257+
final CountDownLatch latch1 = new CountDownLatch(1);
258258

259-
private final CountDownLatch latch2 = new CountDownLatch(1);
259+
final CountDownLatch latch2 = new CountDownLatch(1);
260260

261261
private final KafkaListenerContainerFactory<?> cf;
262262

263-
private List<Foo> received;
263+
volatile List<Foo> received;
264264

265-
private List<String> receivedTopics;
265+
volatile List<String> receivedTopics;
266266

267-
private List<Integer> receivedPartitions;
267+
volatile List<Integer> receivedPartitions;
268268

269269
public Listener(String topic, KafkaListenerContainerFactory<?> cf) {
270270
this.topic = topic;
@@ -302,9 +302,9 @@ public String getTopic() {
302302

303303
public static class Listener3 {
304304

305-
private final CountDownLatch latch1 = new CountDownLatch(1);
305+
final CountDownLatch latch1 = new CountDownLatch(1);
306306

307-
private List<Message<Foo>> received;
307+
volatile List<Message<Foo>> received;
308308

309309
@KafkaListener(topics = "blc3", groupId = "blc3")
310310
public void listen1(List<Message<Foo>> foos) {
@@ -318,11 +318,11 @@ public void listen1(List<Message<Foo>> foos) {
318318

319319
public static class Listener4 {
320320

321-
private final CountDownLatch latch1 = new CountDownLatch(1);
321+
final CountDownLatch latch1 = new CountDownLatch(1);
322322

323-
private List<Foo> received;
323+
volatile List<Foo> received;
324324

325-
private List<Foo> replies;
325+
volatile List<Foo> replies;
326326

327327
@KafkaListener(topics = "blc4", groupId = "blc4")
328328
@SendTo
@@ -351,7 +351,7 @@ public static class Listener5 {
351351

352352
final CountDownLatch latch2 = new CountDownLatch(1);
353353

354-
final List<Foo> received = new ArrayList<>();
354+
final List<Foo> received = Collections.synchronizedList(new ArrayList<>());
355355

356356
volatile String dlt;
357357

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Copyright 2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.kafka.listener;
18+
19+
import static org.assertj.core.api.Assertions.assertThatNoException;
20+
import static org.mockito.ArgumentMatchers.any;
21+
import static org.mockito.ArgumentMatchers.anyString;
22+
import static org.mockito.Mockito.never;
23+
import static org.mockito.Mockito.spy;
24+
import static org.mockito.Mockito.verify;
25+
26+
import java.util.Map;
27+
import java.util.Properties;
28+
29+
import org.apache.commons.logging.LogFactory;
30+
import org.apache.kafka.clients.consumer.ConsumerConfig;
31+
import org.junit.jupiter.api.Test;
32+
33+
import org.springframework.beans.DirectFieldAccessor;
34+
import org.springframework.core.log.LogAccessor;
35+
import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
36+
import org.springframework.kafka.test.EmbeddedKafkaBroker;
37+
import org.springframework.kafka.test.context.EmbeddedKafka;
38+
import org.springframework.kafka.test.utils.KafkaTestUtils;
39+
40+
/**
41+
* @author Gary Russell
42+
* @since 3.0
43+
*
44+
*/
45+
@EmbeddedKafka(topics = "mtccac")
46+
public class MissingTopicCheckOverrideAdminConfigTests {
47+
48+
@Test
49+
void configOverride(EmbeddedKafkaBroker broker) {
50+
Map<String, Object> consumerProps = KafkaTestUtils.consumerProps("grp", "false", broker);
51+
consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "junkjunk");
52+
DefaultKafkaConsumerFactory<Integer, String> cf = new DefaultKafkaConsumerFactory<>(consumerProps);
53+
ContainerProperties props = new ContainerProperties("mtccac");
54+
props.setMissingTopicsFatal(true);
55+
props.getKafkaConsumerProperties().setProperty(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,
56+
broker.getBrokersAsString());
57+
KafkaMessageListenerContainer<Integer, String> container = new KafkaMessageListenerContainer<>(cf, props) {
58+
59+
@Override
60+
public void checkTopics() {
61+
super.checkTopics();
62+
}
63+
64+
};
65+
LogAccessor logger = spy(new LogAccessor(LogFactory.getLog(getClass())));
66+
new DirectFieldAccessor(container).setPropertyValue("logger", logger);
67+
assertThatNoException().isThrownBy(() -> container.checkTopics());
68+
verify(logger, never()).error(any(), anyString());
69+
}
70+
71+
@Test
72+
void configOverrideDefault(EmbeddedKafkaBroker broker) {
73+
Map<String, Object> consumerProps = KafkaTestUtils.consumerProps("grp", "false", broker);
74+
consumerProps.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "junkjunk");
75+
DefaultKafkaConsumerFactory<Integer, String> cf = new DefaultKafkaConsumerFactory<>(consumerProps);
76+
ContainerProperties props = new ContainerProperties("mtccac");
77+
props.setMissingTopicsFatal(true);
78+
/*
79+
* Ensure this works if there are property defaults.
80+
* We have to iterate over the hash table because the user might have
81+
* used put() instead of setProperty().
82+
*/
83+
Properties defaultProperties = new Properties();
84+
defaultProperties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, broker.getBrokersAsString());
85+
Properties properties = new Properties(defaultProperties);
86+
props.setKafkaConsumerProperties(properties);
87+
KafkaMessageListenerContainer<Integer, String> container = new KafkaMessageListenerContainer<>(cf, props) {
88+
89+
@Override
90+
public void checkTopics() {
91+
super.checkTopics();
92+
}
93+
94+
};
95+
LogAccessor logger = spy(new LogAccessor(LogFactory.getLog(getClass())));
96+
new DirectFieldAccessor(container).setPropertyValue("logger", logger);
97+
assertThatNoException().isThrownBy(() -> container.checkTopics());
98+
verify(logger, never()).error(any(), anyString());
99+
}
100+
101+
}

0 commit comments

Comments
 (0)