|
111 | 111 | import org.springframework.kafka.listener.ContainerProperties.AssignmentCommitOption; |
112 | 112 | import org.springframework.kafka.listener.ContainerProperties.EOSMode; |
113 | 113 | import org.springframework.kafka.listener.adapter.AsyncRepliesAware; |
| 114 | +import org.springframework.kafka.listener.adapter.FilteringAware; |
114 | 115 | import org.springframework.kafka.listener.adapter.KafkaBackoffAwareMessageListenerAdapter; |
115 | 116 | import org.springframework.kafka.listener.adapter.RecordMessagingMessageListenerAdapter; |
116 | 117 | import org.springframework.kafka.support.Acknowledgment; |
|
172 | 173 | * @author Christian Fredriksson |
173 | 174 | * @author Timofey Barabanov |
174 | 175 | * @author Janek Lasocki-Biczysko |
| 176 | + * @author Chaedong Im |
175 | 177 | */ |
176 | 178 | public class KafkaMessageListenerContainer<K, V> // NOSONAR line count |
177 | 179 | extends AbstractMessageListenerContainer<K, V> implements ConsumerPauseResumeEventPublisher { |
@@ -677,6 +679,8 @@ private final class ListenerConsumer implements SchedulingAwareRunnable, Consume |
677 | 679 |
|
678 | 680 | private final boolean isRecordAck; |
679 | 681 |
|
| 682 | + private final boolean isRecordFilteredAck; |
| 683 | + |
680 | 684 | private final BlockingQueue<ConsumerRecord<K, V>> acks = new LinkedBlockingQueue<>(); |
681 | 685 |
|
682 | 686 | private final BlockingQueue<TopicPartitionOffset> seeks = new LinkedBlockingQueue<>(); |
@@ -871,6 +875,7 @@ private final class ListenerConsumer implements SchedulingAwareRunnable, Consume |
871 | 875 | this.isManualImmediateAck = AckMode.MANUAL_IMMEDIATE.equals(this.ackMode); |
872 | 876 | this.isAnyManualAck = this.isManualAck || this.isManualImmediateAck; |
873 | 877 | this.isRecordAck = this.ackMode.equals(AckMode.RECORD); |
| 878 | + this.isRecordFilteredAck = this.ackMode.equals(AckMode.RECORD_FILTERED); |
874 | 879 | boolean isOutOfCommit = this.isAnyManualAck && this.asyncReplies; |
875 | 880 | this.offsetsInThisBatch = isOutOfCommit ? new ConcurrentHashMap<>() : null; |
876 | 881 | this.deferredOffsets = isOutOfCommit ? new ConcurrentHashMap<>() : null; |
@@ -933,8 +938,8 @@ else if (listener instanceof MessageListener) { |
933 | 938 | this.isConsumerAwareListener = listenerType.equals(ListenerType.ACKNOWLEDGING_CONSUMER_AWARE) |
934 | 939 | || listenerType.equals(ListenerType.CONSUMER_AWARE); |
935 | 940 | this.commonErrorHandler = determineCommonErrorHandler(); |
936 | | - Assert.state(!this.isBatchListener || !this.isRecordAck, |
937 | | - "Cannot use AckMode.RECORD with a batch listener"); |
| 941 | + Assert.state(!this.isBatchListener || (!this.isRecordAck && !this.isRecordFilteredAck), |
| 942 | + "Cannot use AckMode.RECORD or AckMode.RECORD_FILTERED with a batch listener"); |
938 | 943 | if (this.containerProperties.getScheduler() != null) { |
939 | 944 | this.taskScheduler = this.containerProperties.getScheduler(); |
940 | 945 | this.taskSchedulerExplicitlySet = true; |
@@ -1510,7 +1515,7 @@ protected void handleAsyncFailure() { |
1510 | 1515 | } |
1511 | 1516 |
|
1512 | 1517 | private void doProcessCommits() { |
1513 | | - if (!this.autoCommit && !this.isRecordAck) { |
| 1518 | + if (!this.autoCommit && !this.isRecordAck && !this.isRecordFilteredAck) { |
1514 | 1519 | try { |
1515 | 1520 | processCommits(); |
1516 | 1521 | } |
@@ -2260,7 +2265,7 @@ private List<ConsumerRecord<K, V>> createRecordList(final ConsumerRecords<K, V> |
2260 | 2265 | } |
2261 | 2266 | getAfterRollbackProcessor().clearThreadState(); |
2262 | 2267 | } |
2263 | | - if (!this.autoCommit && !this.isRecordAck) { |
| 2268 | + if (!this.autoCommit && !this.isRecordAck && !this.isRecordFilteredAck) { |
2264 | 2269 | processCommits(); |
2265 | 2270 | } |
2266 | 2271 | } |
@@ -2710,7 +2715,7 @@ private void listenerInfo(final ConsumerRecord<K, V> cRecord) { |
2710 | 2715 | } |
2711 | 2716 |
|
2712 | 2717 | private void handleNack(final ConsumerRecords<K, V> records, final ConsumerRecord<K, V> cRecord) { |
2713 | | - if (!this.autoCommit && !this.isRecordAck) { |
| 2718 | + if (!this.autoCommit && !this.isRecordAck && !this.isRecordFilteredAck) { |
2714 | 2719 | processCommits(); |
2715 | 2720 | } |
2716 | 2721 | List<ConsumerRecord<?, ?>> list = new ArrayList<>(); |
@@ -3060,12 +3065,26 @@ public void checkDeser(final ConsumerRecord<K, V> cRecord, String headerName) { |
3060 | 3065 | } |
3061 | 3066 | } |
3062 | 3067 |
|
| 3068 | + private boolean isRecordFiltered(ConsumerRecord<K, V> cRecord) { |
| 3069 | + Object listener = KafkaMessageListenerContainer.this.getContainerProperties().getMessageListener(); |
| 3070 | + if (listener instanceof FilteringAware<?, ?>) { |
| 3071 | + @SuppressWarnings("unchecked") |
| 3072 | + FilteringAware<K, V> filteringAware = (FilteringAware<K, V>) listener; |
| 3073 | + return filteringAware.wasFiltered(cRecord); |
| 3074 | + } |
| 3075 | + return false; |
| 3076 | + } |
| 3077 | + |
3063 | 3078 | public void ackCurrent(final ConsumerRecord<K, V> cRecord) { |
3064 | 3079 | ackCurrent(cRecord, false); |
3065 | 3080 | } |
3066 | 3081 |
|
3067 | 3082 | public void ackCurrent(final ConsumerRecord<K, V> cRecord, boolean commitRecovered) { |
3068 | | - if (this.isRecordAck && this.producer == null) { |
| 3083 | + if (this.isRecordFilteredAck && isRecordFiltered(cRecord)) { |
| 3084 | + return; |
| 3085 | + } |
| 3086 | + |
| 3087 | + if ((this.isRecordAck || this.isRecordFilteredAck) && this.producer == null) { |
3069 | 3088 | Map<TopicPartition, OffsetAndMetadata> offsetsToCommit = buildSingleCommits(cRecord); |
3070 | 3089 | this.commitLogger.log(() -> COMMITTING + offsetsToCommit); |
3071 | 3090 | commitOffsets(offsetsToCommit); |
|
0 commit comments