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

org.springframework.kafka.config.StreamsBuilderFactoryBean Maven / Gradle / Ivy

There is a newer version: 3.1.4
Show newest version
/*
 * Copyright 2017-2020 the original author or authors.
 *
 * 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
 *
 *      https://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 org.springframework.kafka.config;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import org.apache.commons.logging.LogFactory;
import org.apache.kafka.streams.KafkaClientSupplier;
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.Topology;
import org.apache.kafka.streams.processor.StateRestoreListener;
import org.apache.kafka.streams.processor.internals.DefaultKafkaClientSupplier;

import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.context.SmartLifecycle;
import org.springframework.core.log.LogAccessor;
import org.springframework.kafka.KafkaException;
import org.springframework.kafka.core.CleanupConfig;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * An {@link AbstractFactoryBean} for the {@link StreamsBuilder} instance
 * and lifecycle control for the internal {@link KafkaStreams} instance.
 * 

* A fine grained control on {@link KafkaStreams} can be achieved by * {@link KafkaStreamsCustomizer}s. * * @author Artem Bilan * @author Ivan Ursul * @author Soby Chacko * @author Zach Olauson * @author Nurettin Yilmaz * @author Denis Washington * @author Gary Russell * * @since 1.1.4 */ public class StreamsBuilderFactoryBean extends AbstractFactoryBean implements SmartLifecycle, BeanNameAware { /** * The default {@link Duration} of {@code 10 seconds} for close timeout. * @see KafkaStreams#close(Duration) */ public static final Duration DEFAULT_CLOSE_TIMEOUT = Duration.ofSeconds(10); private static final LogAccessor LOGGER = new LogAccessor(LogFactory.getLog(StreamsBuilderFactoryBean.class)); private static final String STREAMS_CONFIG_MUST_NOT_BE_NULL = "'streamsConfig' must not be null"; private static final String CLEANUP_CONFIG_MUST_NOT_BE_NULL = "'cleanupConfig' must not be null"; private KafkaClientSupplier clientSupplier = new DefaultKafkaClientSupplier(); private Properties properties; private final CleanupConfig cleanupConfig; private final List listeners = new ArrayList<>(); private KafkaStreamsInfrastructureCustomizer infrastructureCustomizer = new KafkaStreamsInfrastructureCustomizer() { }; private KafkaStreamsCustomizer kafkaStreamsCustomizer; private KafkaStreams.StateListener stateListener; private StateRestoreListener stateRestoreListener; private Thread.UncaughtExceptionHandler uncaughtExceptionHandler; private boolean autoStartup = true; private int phase = Integer.MAX_VALUE - 1000; // NOSONAR magic # private Duration closeTimeout = DEFAULT_CLOSE_TIMEOUT; private KafkaStreams kafkaStreams; private volatile boolean running; private Topology topology; private String beanName; /** * Default constructor that creates the factory without configuration * {@link Properties}. It is the factory user's responsibility to properly set * {@link Properties} using * {@link StreamsBuilderFactoryBean#setStreamsConfiguration(Properties)}. * @since 2.1.3. */ public StreamsBuilderFactoryBean() { this.cleanupConfig = new CleanupConfig(); } /** * Construct an instance with the supplied streams configuration and * clean up configuration. * @param streamsConfig the streams configuration. * @param cleanupConfig the cleanup configuration. * @since 2.2 */ public StreamsBuilderFactoryBean(KafkaStreamsConfiguration streamsConfig, CleanupConfig cleanupConfig) { Assert.notNull(streamsConfig, STREAMS_CONFIG_MUST_NOT_BE_NULL); Assert.notNull(cleanupConfig, CLEANUP_CONFIG_MUST_NOT_BE_NULL); this.properties = streamsConfig.asProperties(); this.cleanupConfig = cleanupConfig; } /** * Construct an instance with the supplied streams configuration. * @param streamsConfig the streams configuration. * @since 2.2 */ public StreamsBuilderFactoryBean(KafkaStreamsConfiguration streamsConfig) { this(streamsConfig, new CleanupConfig()); } @Override public synchronized void setBeanName(String name) { this.beanName = name; } /** * Set the streams configuration {@link Properties} on this factory. * @param streamsConfig the streams configuration. * @since 2.2 */ public void setStreamsConfiguration(Properties streamsConfig) { Assert.notNull(streamsConfig, STREAMS_CONFIG_MUST_NOT_BE_NULL); this.properties = streamsConfig; } @Nullable public Properties getStreamsConfiguration() { return this.properties; // NOSONAR - inconsistent synchronization } public void setClientSupplier(KafkaClientSupplier clientSupplier) { Assert.notNull(clientSupplier, "'clientSupplier' must not be null"); this.clientSupplier = clientSupplier; // NOSONAR (sync) } /** * Set a customizer to configure the builder and/or topology before creating the stream. * @param infrastructureCustomizer the customizer * @since 2.4.1 */ public void setInfrastructureCustomizer(KafkaStreamsInfrastructureCustomizer infrastructureCustomizer) { Assert.notNull(infrastructureCustomizer, "'infrastructureCustomizer' must not be null"); this.infrastructureCustomizer = infrastructureCustomizer; // NOSONAR (sync) } /** * Specify a {@link KafkaStreamsCustomizer} to customize a {@link KafkaStreams} * instance during {@link #start()}. * @param kafkaStreamsCustomizer the {@link KafkaStreamsCustomizer} to use. * @since 2.1.5 */ public void setKafkaStreamsCustomizer(KafkaStreamsCustomizer kafkaStreamsCustomizer) { Assert.notNull(kafkaStreamsCustomizer, "'kafkaStreamsCustomizer' must not be null"); this.kafkaStreamsCustomizer = kafkaStreamsCustomizer; // NOSONAR (sync) } public void setStateListener(KafkaStreams.StateListener stateListener) { this.stateListener = stateListener; // NOSONAR (sync) } public void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler exceptionHandler) { this.uncaughtExceptionHandler = exceptionHandler; // NOSONAR (sync) } public void setStateRestoreListener(StateRestoreListener stateRestoreListener) { this.stateRestoreListener = stateRestoreListener; // NOSONAR (sync) } /** * Specify the timeout in seconds for the {@link KafkaStreams#close(Duration)} * operation. Defaults to {@link #DEFAULT_CLOSE_TIMEOUT} seconds. * @param closeTimeout the timeout for close in seconds. * @see KafkaStreams#close(Duration) */ public void setCloseTimeout(int closeTimeout) { this.closeTimeout = Duration.ofSeconds(closeTimeout); // NOSONAR (sync) } /** * Providing access to the associated {@link Topology} of this * {@link StreamsBuilderFactoryBean}. * @return {@link Topology} object * @since 2.4.4 */ public Topology getTopology() { return this.topology; } @Override public Class getObjectType() { return StreamsBuilder.class; } public void setAutoStartup(boolean autoStartup) { this.autoStartup = autoStartup; } public void setPhase(int phase) { this.phase = phase; } @Override public int getPhase() { return this.phase; } /** * Get a managed by this {@link StreamsBuilderFactoryBean} {@link KafkaStreams} instance. * @return KafkaStreams managed instance; * may be null if this {@link StreamsBuilderFactoryBean} hasn't been started. * @since 1.1.4 */ public synchronized KafkaStreams getKafkaStreams() { return this.kafkaStreams; } /** * Get the current list of listeners. * @return the listeners. * @since 2.5.3 */ public List getListeners() { return Collections.unmodifiableList(this.listeners); } /** * Add a {@link Listener} which will be called after starting and stopping the * streams. * @param listener the listener. * @since 2.5.3 */ public void addListener(Listener listener) { Assert.notNull(listener, "'listener' cannot be null"); this.listeners.add(listener); } /** * Remove a listener. * @param listener the listener. * @return true if removed. * @since 2.5.3 */ public boolean removeListener(Listener listener) { return this.listeners.remove(listener); } @Override protected synchronized StreamsBuilder createInstance() { if (this.autoStartup) { Assert.state(this.properties != null, "streams configuration properties must not be null"); } StreamsBuilder builder = new StreamsBuilder(); this.infrastructureCustomizer.configureBuilder(builder); return builder; } @Override public boolean isAutoStartup() { return this.autoStartup; } @Override public void stop(Runnable callback) { stop(); if (callback != null) { callback.run(); } } @Override public synchronized void start() { if (!this.running) { try { Assert.state(this.properties != null, "streams configuration properties must not be null"); Topology topology = getObject().build(this.properties); // NOSONAR: getObject() cannot return null this.infrastructureCustomizer.configureTopology(topology); this.topology = topology; LOGGER.debug(() -> topology.describe().toString()); this.kafkaStreams = new KafkaStreams(topology, this.properties, this.clientSupplier); this.kafkaStreams.setStateListener(this.stateListener); this.kafkaStreams.setGlobalStateRestoreListener(this.stateRestoreListener); this.kafkaStreams.setUncaughtExceptionHandler(this.uncaughtExceptionHandler); if (this.kafkaStreamsCustomizer != null) { this.kafkaStreamsCustomizer.customize(this.kafkaStreams); } if (this.cleanupConfig.cleanupOnStart()) { this.kafkaStreams.cleanUp(); } this.kafkaStreams.start(); this.listeners.forEach(listener -> listener.streamsAdded(this.beanName, this.kafkaStreams)); this.running = true; } catch (Exception e) { throw new KafkaException("Could not start stream: ", e); } } } @Override public synchronized void stop() { if (this.running) { try { if (this.kafkaStreams != null) { this.kafkaStreams.close(this.closeTimeout); if (this.cleanupConfig.cleanupOnStop()) { this.kafkaStreams.cleanUp(); } this.listeners.forEach(listener -> listener.streamsRemoved(this.beanName, this.kafkaStreams)); this.kafkaStreams = null; } } catch (Exception e) { LOGGER.error(e, "Failed to stop streams"); } finally { this.running = false; } } } @Override public synchronized boolean isRunning() { return this.running; } /** * Called whenever a {@link KafkaStreams} is added or removed. * * @since 2.5.3 * */ public interface Listener { /** * A new {@link KafkaStreams} was created. * @param id the streams id (factory bean name). * @param streams the streams; */ default void streamsAdded(String id, KafkaStreams streams) { } /** * An existing {@link KafkaStreams} was removed. * @param id the streams id (factory bean name). * @param streams the streams; */ default void streamsRemoved(String id, KafkaStreams streams) { } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy