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

io.wizzie.normalizer.builder.StreamBuilder Maven / Gradle / Ivy

The newest version!
package io.wizzie.normalizer.builder;

import io.wizzie.bootstrapper.builder.Config;
import io.wizzie.metrics.MetricsManager;
import io.wizzie.normalizer.base.builder.config.ConfigProperties;
import io.wizzie.normalizer.base.utils.Constants;
import io.wizzie.normalizer.exceptions.PlanBuilderException;
import io.wizzie.normalizer.exceptions.TryToDoLoopException;
import io.wizzie.normalizer.funcs.*;
import io.wizzie.normalizer.model.FunctionModel;
import io.wizzie.normalizer.model.PlanModel;
import io.wizzie.normalizer.model.SinkModel;
import io.wizzie.normalizer.model.StreamModel;
import io.wizzie.normalizer.serializers.JsonSerde;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.kstream.KStream;
import org.apache.kafka.streams.state.StoreBuilder;
import org.apache.kafka.streams.state.Stores;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.stream.Collectors;

import static org.apache.kafka.streams.StreamsConfig.APPLICATION_ID_CONFIG;

public class StreamBuilder {
    String appId;
    MetricsManager metricsManager;
    Config config;

    public StreamBuilder(Config config, MetricsManager metricsManager) {
        this.appId = config.get(APPLICATION_ID_CONFIG);
        this.config = config;
        this.metricsManager = metricsManager;
    }

    private static final Logger log = LoggerFactory.getLogger(StreamBuilder.class);

    private Map>> kStreams = new HashMap<>();
    private Map> streamFunctions = new HashMap<>();
    private Map> streamFilters = new HashMap<>();
    private Map streamIterationCreation = new HashMap<>();
    private Set streamEdges = new HashSet<>();
    private Set usedStores = new HashSet<>();
    private Set addedFuncsToStreams = new HashSet<>();
    private Set addedSinksToStreams = new HashSet<>();
    private Boolean addedNewStream = true;

    public StreamsBuilder builder(PlanModel model) throws PlanBuilderException {
        model.validate(config);

        clean();

        StreamsBuilder builder = new StreamsBuilder();
        createInputStreams(builder, model);

        for (int iteration = 0; addedNewStream; iteration++) {
            log.info("*** Iteration [{}]", iteration);
            addedNewStream = false;
            addFuncsToStreams(builder, model, iteration);
            addSinksToStreams(model, iteration);
        }

        return builder;
    }

    public KStream> getKStream(String streamName) {
        return kStreams.get(streamName);
    }

    public Map getFunctions(String streamName) {
        return streamFunctions.get(streamName);
    }

    public Map getFilters(String streamName) {
        return streamFilters.get(streamName);
    }

    public Set usedStores() {
        return usedStores;
    }

    public void close() {
        streamFunctions.forEach((stream, functions) -> functions.forEach((name, function) -> function.stop()));
        clean();
    }

    private void createInputStreams(StreamsBuilder builder, PlanModel model) {
        for (Map.Entry> inputs : model.getInputs().entrySet()) {
            String topic = inputs.getKey();

            if (config.getOrDefault(ConfigProperties.MULTI_ID, false)) {
                topic = String.format("%s_%s", appId, topic);
            }

            KStream> kstream = builder.stream(topic);

            for (String stream : inputs.getValue()) {
                if (!kStreams.containsKey(stream)) {
                    log.info("Creating stream [{}]", stream);
                    kStreams.put(stream, kstream.filter((key, value) -> key != null || value != null));
                    streamIterationCreation.put(stream, 0);
                } else {
                    kStreams.put(stream, kStreams.get(stream).merge(kstream));
                }

                streamEdges.add(String.format("%s->%s", topic, stream));
            }
        }
    }

    private void addFuncsToStreams(StreamsBuilder builder, PlanModel model, Integer iteration) {
        for (Map.Entry stream : model.getStreams().entrySet()) {
            if (!addedFuncsToStreams.contains(stream.getKey()) && kStreams.containsKey(stream.getKey())) {
                List funcModels = stream.getValue().getFuncs();
                if (funcModels != null) {
                    for (FunctionModel funcModel : funcModels) {
                        KStream> kStream = kStreams.get(stream.getKey());
                        String name = funcModel.getName();
                        String className = funcModel.getClassName();
                        Map properties = funcModel.getProperties();
                        List stores = funcModel.getStores();

                        if (stores != null) {
                            properties.put(Constants.__STORES, stores);
                            properties.put(Constants.__APP_ID, appId);

                            stores = stores.stream()
                                    .map(store -> String.format("%s_%s", appId, store))
                                    .collect(Collectors.toList());

                            stores.forEach(store -> {
                                if (!usedStores.contains(store)) {
                                    StoreBuilder storeSupplier = Stores
                                            .keyValueStoreBuilder(Stores.persistentKeyValueStore(store),
                                                    Serdes.String(),
                                                    new JsonSerde()
                                            );

                                    builder.addStateStore(storeSupplier);
                                    usedStores.add(store);
                                }
                            });
                        }

                        try {
                            log.info("Add function [{}] to stream [{}]", name, stream.getKey());
                            Function func = makeFunction(className, properties);
                            if (func instanceof MapperFunction) {
                                kStream = kStream.map((MapperFunction) func);
                            } else if (func instanceof FlatMapperFunction) {
                                kStream = kStream.flatMap((FlatMapperFunction) func);
                            } else if (func instanceof MapperStoreFunction) {
                                kStream = kStream.transform(() ->
                                        (MapperStoreFunction) func, stores.toArray(new String[stores.size()])
                                );
                            } else if (func instanceof FilterFunc) {
                                FilterFunc filterFunc = (FilterFunc) func;
                                kStream = kStream.filter(filterFunc);
                            }

                            Map functions = streamFunctions.get(stream.getKey());
                            if (functions == null) functions = new HashMap<>();
                            functions.put(name, func);

                            streamFunctions.put(stream.getKey(), functions);
                            kStreams.put(stream.getKey(), kStream);

                            if (!streamIterationCreation.containsKey(stream.getKey())) {
                                streamIterationCreation.put(stream.getKey(), iteration);
                            }
                        } catch (ClassNotFoundException e) {
                            log.error("Couldn't find the class associated with the function {}", className);
                        } catch (InstantiationException | IllegalAccessException e) {
                            log.error("Couldn't create the instance associated with the function " + className, e);
                        }
                    }
                }

                addedFuncsToStreams.add(stream.getKey());
            } else {
                if (!kStreams.containsKey(stream.getKey())) {
                    log.debug("Stream {} is to later iteration.", stream.getKey());
                }
            }
        }
    }

    private void addSinksToStreams(PlanModel model, Integer iteration) throws TryToDoLoopException {
        List generatedStreams = new ArrayList<>();
        for (Map.Entry stream : model.getStreams().entrySet()) {
            Map filters = new HashMap<>();
            if (!addedSinksToStreams.contains(stream.getKey()) && !generatedStreams.contains(stream.getKey())) {
                List sinks = stream.getValue().getSinks();
                for (SinkModel sink : sinks) {
                    KStream> kStream = kStreams.get(stream.getKey());
                    if (kStream != null) {
                        kStream = kStream.filter((key, value) -> key != null || value != null);
                        log.info("Send to {} [{}]", sink.getType(), sink.getTopic());

                        if (!sink.getPartitionBy().equals(SinkModel.PARTITION_BY_KEY)) {
                            kStream = kStream.selectKey((key, value) -> {
                                Object newKey = null;
                                if (value != null) {
                                    newKey = value.get(sink.getPartitionBy());
                                }
                                if (newKey != null) return newKey.toString();
                                else {
                                    log.trace("Partition key {} isn't on message {}",
                                            sink.getPartitionBy(), value);
                                    return key;
                                }
                            });
                        }

                        FunctionModel filterModel = sink.getFilter();
                        if (filterModel != null) {
                            String className = filterModel.getClassName();
                            try {
                                FilterFunc filter = (FilterFunc) makeFunction(className, filterModel.getProperties());
                                kStream = kStream.filter(filter);
                                filters.put(String.format("%s-%s", sink.getType(), sink.getTopic()), filter);
                            } catch (ClassNotFoundException e) {
                                log.error("Couldn't find the class associated with the function {}", className);
                            } catch (InstantiationException | IllegalAccessException e) {
                                log.error("Couldn't create the instance associated with the function " + className, e);
                            }
                        }

                        if (sink.getType().equals(SinkModel.KAFKA_TYPE)) {
                            String topic = sink.getTopic();

                            if (config.getOrDefault(ConfigProperties.MULTI_ID, false)) {
                                topic = String.format("%s_%s", appId, topic);
                            }

                            streamIterationCreation.put(stream.getKey(), iteration);
                            streamEdges.add(String.format("%s->%s", stream.getKey(), topic));

                            kStream.to(topic);
                        } else if (sink.getType().equals(SinkModel.STREAM_TYPE)) {
                            String newStreamName = sink.getTopic();

                            if (newStreamName.equals(stream.getKey())) {
                                throw new TryToDoLoopException(
                                        "Loop from [" + stream.getKey() + "] to [" + newStreamName + "]"
                                );
                            }

                            if (!kStreams.containsKey(newStreamName)) {
                                addedNewStream = true;
                                KStream> newBranch = kStream.branch((key, value) -> true)[0];
                                kStreams.put(newStreamName, newBranch);

                                if (!streamIterationCreation.containsKey(newStreamName)) {
                                    streamIterationCreation.put(newStreamName, iteration);
                                }

                                streamEdges.add(String.format("%s->%s", stream.getKey(), sink.getTopic()));
                                log.info("Creating stream [{}]", sink.getTopic());
                                generatedStreams.add(sink.getTopic());
                            } else {
                                int sinkStreamIteration = streamIterationCreation.get(newStreamName);

                                if (
                                        (((sinkStreamIteration + 1) == iteration) || (sinkStreamIteration == iteration)) && !streamEdges.contains(String.format("%s->%s", newStreamName, stream.getKey()))) {
                                    KStream> mergeKStream = kStreams.get(newStreamName).merge(kStream);
                                    kStreams.put(newStreamName, mergeKStream);
                                    streamEdges.add(String.format("%s->%s", stream.getKey(), newStreamName));
                                } else {
                                    throw new TryToDoLoopException(
                                            "Loop from [" + stream.getKey() + "] to [" + newStreamName + "]"
                                    );
                                }

                            }
                        }
                        streamFilters.put(stream.getKey(), filters);
                        addedSinksToStreams.add(stream.getKey());
                    }
                }
            }
        }
        generatedStreams.clear();
    }

    private void clean() {
        addedSinksToStreams.clear();
        streamFunctions.clear();
        addedFuncsToStreams.clear();
        usedStores.clear();
        kStreams.clear();
        streamEdges.clear();
        streamIterationCreation.clear();
        addedNewStream = true;
    }

    private Function makeFunction(String className, Map properties)
            throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        Class funcClass = Class.forName(className);
        Function func = (Function) funcClass.newInstance();
        func.init(properties, metricsManager);
        return func;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy