/**
* Copyright 2016-2017 Sixt GmbH & Co. Autovermietung KG
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain a
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.sixt.service.framework.kafka.messaging;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.TopicPartition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* AssignedPartitions represents the set of PartitionProcessors for the partitions assigned to a certain consumer.
* <p>
* Read records are dispatched to the right processor. It aggregates over the individual processors to get the set of
* committed offset, paused or resumed partitions.
* <p>
* Additionally it manages creation, termination and removal of PartitionProcessors as partitions are distributed across
* consumers.
* <p>
* Thread safety policy: single-thread use by MessagingConsumer
*/
final class AssignedPartitions {
private static final Logger logger = LoggerFactory.getLogger(AssignedPartitions.class);
private final PartitionProcessorFactory processorFactory;
private final Map<TopicPartition, PartitionProcessor> processors = new HashMap<>();
AssignedPartitions(PartitionProcessorFactory processorFactory) {
this.processorFactory = processorFactory;
}
Set<TopicPartition> allPartitions() {
Set<TopicPartition> assignedPartitions = new HashSet<>();
processors.forEach((key, partition) -> assignedPartitions.add(key));
return assignedPartitions;
}
void assignNewPartitions(Collection<TopicPartition> assignedPartitions) {
assignedPartitions.forEach((key) -> assignNewPartition(key));
}
PartitionProcessor assignNewPartition(TopicPartition partitionKey) {
logger.debug("Assigning new PartitionProcessor for partition {}", partitionKey);
PartitionProcessor proccessor = processorFactory.newProcessorFor(partitionKey);
processors.put(partitionKey, proccessor);
return proccessor;
}
void enqueue(ConsumerRecords<String, byte[]> records) {
records.forEach((record) -> {
TopicPartition partitionKey = new TopicPartition(record.topic(), record.partition());
PartitionProcessor processor = processors.get(partitionKey);
if (processor == null) {
processor = assignNewPartition(partitionKey);
}
processor.enqueue(record);
});
}
Map<TopicPartition, OffsetAndMetadata> offsetsToBeCommitted() {
Map<TopicPartition, OffsetAndMetadata> commitOffsets = new HashMap<>();
processors.forEach((key, processor) -> {
if (processor.hasUncommittedMessages()) {
commitOffsets.put(key, new OffsetAndMetadata(processor.getCommitOffsetAndClear()));
}
});
return commitOffsets;
}
Collection<TopicPartition> partitionsToBePaused() {
List<TopicPartition> pausedPartitions = new ArrayList<>();
processors.forEach((key, processor) -> {
if (processor.isPaused()) {
pausedPartitions.add(key);
}
});
return pausedPartitions;
}
Collection<TopicPartition> partitionsToBeResumed() {
List<TopicPartition> resumeablePartitions = new ArrayList<>();
processors.forEach((key, processor) -> {
if (processor.shouldResume()) {
resumeablePartitions.add(key);
}
});
return resumeablePartitions;
}
void stopProcessing(Collection<TopicPartition> partitions) {
partitions.forEach((key) -> {
PartitionProcessor processor = processors.get(key);
if (processor == null) {
logger.warn("Ignored operation: trying to stop a non-existing processor for partition {}", key);
return;
}
processor.stopProcessing();
});
}
void waitForHandlersToComplete(Collection<TopicPartition> partitions, long timeoutMillis) {
partitions.forEach((key) -> {
PartitionProcessor processor = processors.get(key);
if (processor == null) {
logger.warn("Ignored operation: trying to waitForHandlersToTerminate on a non-existing processor for partition {}", key);
return;
}
processor.waitForHandlersToTerminate(timeoutMillis);
});
}
void removePartitions(Collection<TopicPartition> revokedPartitions) {
revokedPartitions.forEach((key) -> {
PartitionProcessor processor = processors.get(key);
if (processor == null) {
return; // idempotent
}
if (!processor.isTerminated()) {
throw new IllegalStateException("Processor must be terminated before removing it.");
}
logger.debug("Removing PartitionProcessor for partition {}", key);
processors.remove(key);
});
}
Collection<PartitionProcessor> allProcessors() {
Collection<PartitionProcessor> allProcessors = new ArrayList<>();
processors.forEach((key, processor) -> {
allProcessors.add(processor);
});
return allProcessors;
}
}