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

org.apache.kafka.streams.processor.internals.ProcessorTopology Maven / Gradle / Ivy

There is a newer version: 3.8.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.kafka.streams.processor.internals;

import org.apache.kafka.streams.processor.StateStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class ProcessorTopology {
    private final Logger log = LoggerFactory.getLogger(ProcessorTopology.class);

    private final List> processorNodes;
    private final Map> sourceNodesByName;
    private final Map> sourceNodesByTopic;
    private final Map> sinksByTopic;
    private final Set terminalNodes;
    private final List stateStores;
    private final Set repartitionTopics;

    // the following contains entries for the entire topology, eg stores that do not belong to this ProcessorTopology
    private final List globalStateStores;
    private final Map storeToChangelogTopic;

    public ProcessorTopology(final List> processorNodes,
                             final Map> sourceNodesByTopic,
                             final Map> sinksByTopic,
                             final List stateStores,
                             final List globalStateStores,
                             final Map storeToChangelogTopic,
                             final Set repartitionTopics) {
        this.processorNodes = Collections.unmodifiableList(processorNodes);
        this.sourceNodesByTopic = new HashMap<>(sourceNodesByTopic);
        this.sinksByTopic = Collections.unmodifiableMap(sinksByTopic);
        this.stateStores = Collections.unmodifiableList(stateStores);
        this.globalStateStores = Collections.unmodifiableList(globalStateStores);
        this.storeToChangelogTopic = Collections.unmodifiableMap(storeToChangelogTopic);
        this.repartitionTopics = Collections.unmodifiableSet(repartitionTopics);

        this.terminalNodes = new HashSet<>();
        for (final ProcessorNode node : processorNodes) {
            if (node.isTerminalNode()) {
                terminalNodes.add(node.name());
            }
        }

        this.sourceNodesByName = new HashMap<>();
        for (final SourceNode source : sourceNodesByTopic.values()) {
            sourceNodesByName.put(source.name(), source);
        }
    }

    public Set sourceTopics() {
        return sourceNodesByTopic.keySet();
    }

    public SourceNode source(final String topic) {
        return sourceNodesByTopic.get(topic);
    }

    public Set> sources() {
        return new HashSet<>(sourceNodesByTopic.values());
    }

    public Set sinkTopics() {
        return sinksByTopic.keySet();
    }

    public SinkNode sink(final String topic) {
        return sinksByTopic.get(topic);
    }

    public Set terminalNodes() {
        return terminalNodes;
    }

    public List> processors() {
        return processorNodes;
    }

    public List stateStores() {
        return stateStores;
    }

    public List globalStateStores() {
        return Collections.unmodifiableList(globalStateStores);
    }

    public Map storeToChangelogTopic() {
        return Collections.unmodifiableMap(storeToChangelogTopic);
    }

    boolean isRepartitionTopic(final String topic) {
        return repartitionTopics.contains(topic);
    }

    boolean hasStateWithChangelogs() {
        for (final StateStore stateStore : stateStores) {
            if (storeToChangelogTopic.containsKey(stateStore.name())) {
                return true;
            }
        }
        return false;
    }

    public boolean hasPersistentLocalStore() {
        for (final StateStore store : stateStores) {
            if (store.persistent()) {
                return true;
            }
        }
        return false;
    }

    public boolean hasPersistentGlobalStore() {
        for (final StateStore store : globalStateStores) {
            if (store.persistent()) {
                return true;
            }
        }
        return false;
    }

    public void updateSourceTopics(final Map> allSourceTopicsByNodeName) {
        sourceNodesByTopic.clear();
        for (final Map.Entry> sourceNodeEntry : sourceNodesByName.entrySet()) {
            final String sourceNodeName = sourceNodeEntry.getKey();
            final SourceNode sourceNode = sourceNodeEntry.getValue();

            final List updatedSourceTopics = allSourceTopicsByNodeName.get(sourceNodeName);
            if (updatedSourceTopics == null) {
                log.error("Unable to find source node {} in updated topics map {}",
                          sourceNodeName, allSourceTopicsByNodeName);
                throw new IllegalStateException("Node " + sourceNodeName + " not found in full topology");
            }

            log.trace("Updating source node {} with new topics {}", sourceNodeName, updatedSourceTopics);
            for (final String topic : updatedSourceTopics) {
                if (sourceNodesByTopic.containsKey(topic)) {
                    log.error("Tried to subscribe topic {} to two nodes when updating topics from {}",
                              topic, allSourceTopicsByNodeName);
                    throw new IllegalStateException("Topic " + topic + " was already registered to source node "
                                                        + sourceNodesByTopic.get(topic).name());
                }
                sourceNodesByTopic.put(topic, sourceNode);
            }
        }
    }

    private String childrenToString(final String indent, final List> children) {
        if (children == null || children.isEmpty()) {
            return "";
        }

        final StringBuilder sb = new StringBuilder(indent + "\tchildren:\t[");
        for (final ProcessorNode child : children) {
            sb.append(child.name());
            sb.append(", ");
        }
        sb.setLength(sb.length() - 2);  // remove the last comma
        sb.append("]\n");

        // recursively print children
        for (final ProcessorNode child : children) {
            sb.append(child.toString(indent)).append(childrenToString(indent, child.children()));
        }
        return sb.toString();
    }

    /**
     * Produces a string representation containing useful information this topology starting with the given indent.
     * This is useful in debugging scenarios.
     * @return A string representation of this instance.
     */
    @Override
    public String toString() {
        return toString("");
    }

    /**
     * Produces a string representation containing useful information this topology.
     * This is useful in debugging scenarios.
     * @return A string representation of this instance.
     */
    public String toString(final String indent) {
        final Map, List> sourceToTopics = new HashMap<>();
        for (final Map.Entry> sourceNodeEntry : sourceNodesByTopic.entrySet()) {
            final String topic = sourceNodeEntry.getKey();
            final SourceNode source = sourceNodeEntry.getValue();
            sourceToTopics.computeIfAbsent(source, s -> new ArrayList<>());
            sourceToTopics.get(source).add(topic);
        }

        final StringBuilder sb = new StringBuilder(indent + "ProcessorTopology:\n");

        // start from sources
        for (final Map.Entry, List> sourceNodeEntry : sourceToTopics.entrySet()) {
            final SourceNode source = sourceNodeEntry.getKey();
            final List topics = sourceNodeEntry.getValue();
            sb.append(source.toString(indent + "\t"))
                .append(topicsToString(indent + "\t", topics))
                .append(childrenToString(indent + "\t", source.children()));
        }
        return sb.toString();
    }

    private static String topicsToString(final String indent, final List topics) {
        final StringBuilder sb = new StringBuilder();
        sb.append(indent).append("\ttopics:\t\t[");
        for (final String topic : topics) {
            sb.append(topic);
            sb.append(", ");
        }
        sb.setLength(sb.length() - 2);  // remove the last comma
        sb.append("]\n");
        return sb.toString();
    }

    // for testing only
    public Set processorConnectedStateStores(final String processorName) {
        for (final ProcessorNode node : processorNodes) {
            if (node.name().equals(processorName)) {
                return node.stateStores;
            }
        }

        return Collections.emptySet();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy