All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.quarkiverse.kafkastreamsprocessor.impl.TopologyProducer Maven / Gradle / Ivy

There is a newer version: 4.0.2
Show newest version
/*-
 * #%L
 * Quarkus Kafka Streams Processor
 * %%
 * Copyright (C) 2024 Amadeus s.a.s.
 * %%
 * 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.
 * #L%
 */
package io.quarkiverse.kafkastreamsprocessor.impl;

import java.util.Map;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Instance;
import jakarta.enterprise.inject.Produces;
import jakarta.enterprise.inject.spi.BeanManager;
import jakarta.inject.Inject;

import org.apache.kafka.common.serialization.StringDeserializer;
import org.apache.kafka.common.serialization.StringSerializer;
import org.apache.kafka.streams.KafkaClientSupplier;
import org.apache.kafka.streams.Topology;
import org.apache.kafka.streams.processor.api.Processor;
import org.apache.kafka.streams.processor.api.ProcessorSupplier;

import io.quarkiverse.kafkastreamsprocessor.api.SinkToTopicMappingBuilder;
import io.quarkiverse.kafkastreamsprocessor.api.SourceToTopicsMappingBuilder;
import io.quarkiverse.kafkastreamsprocessor.api.configuration.ConfigurationCustomizer;
import io.quarkiverse.kafkastreamsprocessor.api.decorator.producer.ProducerOnSendInterceptor;
import io.quarkiverse.kafkastreamsprocessor.api.properties.KStreamsProcessorConfig;
import io.quarkiverse.kafkastreamsprocessor.impl.configuration.DefaultConfigurationCustomizer;
import io.quarkiverse.kafkastreamsprocessor.impl.configuration.DefaultTopologySerdesConfiguration;
import io.quarkiverse.kafkastreamsprocessor.impl.configuration.TopologyConfigurationImpl;
import io.quarkiverse.kafkastreamsprocessor.impl.configuration.TypeUtils;

/**
 * Processor must be annotated with {@link io.quarkiverse.kafkastreamsprocessor.api.Processor}
 */
@ApplicationScoped
public class TopologyProducer {
    /**
     * Default name attached to the unique {@link Processor} this topology produces
     */
    public static final String PROCESSOR_NAME = "Processor";

    /**
     * Default sink name of the dead-letter queue
     */
    public static final String DLQ_SINK_NAME = "DLQ";

    /**
     * Class containing the configuration related to kafka streams processor
     */
    private final KStreamsProcessorConfig kStreamsProcessorConfig;

    /**
     * The configuration customizer if any defined by the microservice.
     * 

* If not defined the {@link DefaultConfigurationCustomizer} is injected. *

*/ private final ConfigurationCustomizer configCustomizer; /** * The source configuration bean which produces the mapping between source and their respective topics */ private final SourceToTopicsMappingBuilder sourceToTopicsMappingBuilder; /** * The sink configuration bean which resolves the mapping between sink and their respective Kafka topic */ private final SinkToTopicMappingBuilder sinkToTopicMappingBuilder; /** * Producer interceptor list that allow to intercept the production of messages to Kafka */ private final Instance interceptors; /** * Injection constructor * * @param kStreamsProcessorConfig * Class containing the configuration related to kafka streams processor * @param configCustomizer * The configuration customizer if any defined by the microservice. * @param sourceToTopicsMappingBuilder * The source configuration bean which produces the mapping between source and their respective topics * @param sinkToTopicMappingBuilder * The sink configuration bean which resolves the mapping between sink and their respective Kafka topic * @param interceptors * Producer interceptor list that allow to intercept the production of messages to Kafka */ @Inject public TopologyProducer(KStreamsProcessorConfig kStreamsProcessorConfig, ConfigurationCustomizer configCustomizer, SourceToTopicsMappingBuilder sourceToTopicsMappingBuilder, SinkToTopicMappingBuilder sinkToTopicMappingBuilder, Instance interceptors) { this.kStreamsProcessorConfig = kStreamsProcessorConfig; this.configCustomizer = configCustomizer; this.sourceToTopicsMappingBuilder = sourceToTopicsMappingBuilder; this.sinkToTopicMappingBuilder = sinkToTopicMappingBuilder; this.interceptors = interceptors; } /** * Injection of the {@link KafkaClientSupplier} that decorated client to intercept message production with the * {@link ProducerOnSendInterceptor} defined. */ @Produces public KafkaClientSupplier kafkaClientSupplier() { return new KafkaClientSupplierDecorator(interceptors); } /** * Produces the final {@link TopologyConfigurationImpl} object * * @param beanManager * CDI's {@link BeanManager} instance * @param defaultConfiguration * the default serdes configuration of the framework * @return the resolved instance */ @Produces public TopologyConfigurationImpl configuration(BeanManager beanManager, DefaultTopologySerdesConfiguration defaultConfiguration) { // Configuration for the Topology we build: // Step 1: initialize the configuration TopologyConfigurationImpl configuration = initializeConfiguration(beanManager); // Step 2: apply default configuration defaultConfiguration.apply(configuration); // Step 3: if an alternative customizer is provided, then apply it (default does nothing) configCustomizer.fillConfiguration(configuration); return configuration; } private static TopologyConfigurationImpl initializeConfiguration(BeanManager beanManager) { Class processorType = TypeUtils.reifiedProcessorType(beanManager); Class payloadType = TypeUtils.extractPayloadType(processorType); if (payloadType == null || Object.class.equals(payloadType)) { throw new IllegalArgumentException( "Could not determine payload type of Processor class " + processorType.getName()); } return new TopologyConfigurationImpl(payloadType); } /** * Based on the topology configured, build the final {@link Topology} for Kafka Streams. * * @param configuration * the configuration built which resolves serdes and state stores * @param kStreamProcessorSupplier * this framework's {@link ProcessorSupplier} responsible for decoration * @return the final {@link Topology} */ @Produces public Topology topology(TopologyConfigurationImpl configuration, KStreamProcessorSupplier kStreamProcessorSupplier) { Map sourceToTopicMapping = sourceToTopicsMappingBuilder.sourceToTopicsMapping(); Map sinkToTopicMapping = sinkToTopicMappingBuilder.sinkToTopicMapping(); // Now we can build the Topology ! Topology topology = new Topology(); sourceToTopicMapping.forEach((String source, String[] topics) -> topology.addSource(source, new StringDeserializer(), configuration.getSourceValueSerde().deserializer(), topics)); topology.addProcessor(PROCESSOR_NAME, kStreamProcessorSupplier, sourceToTopicMapping.keySet().toArray(new String[] {})); sinkToTopicMapping.forEach((String sink, String topic) -> topology.addSink(sink, topic, new StringSerializer(), configuration.getSinkValueSerializer(), PROCESSOR_NAME)); if (kStreamsProcessorConfig.dlq().topic().isPresent()) { topology.addSink(DLQ_SINK_NAME, kStreamsProcessorConfig.dlq().topic().get(), new StringSerializer(), configuration.getSourceValueSerde().serializer(), PROCESSOR_NAME); } configuration.getStoreConfigurations() .forEach(storeConfiguration -> topology.addStateStore(storeConfiguration.getStoreBuilder(), PROCESSOR_NAME)); return topology; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy