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

io.smallrye.reactive.messaging.providers.wiring.Wiring Maven / Gradle / Ivy

package io.smallrye.reactive.messaging.providers.wiring;

import static io.smallrye.reactive.messaging.providers.helpers.CDIUtils.getSortedInstances;

import java.util.*;
import java.util.concurrent.Flow;
import java.util.concurrent.Flow.Publisher;
import java.util.stream.Collectors;

import jakarta.annotation.PreDestroy;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.reactive.messaging.Message;

import io.smallrye.mutiny.Multi;
import io.smallrye.reactive.messaging.ChannelRegistry;
import io.smallrye.reactive.messaging.EmitterConfiguration;
import io.smallrye.reactive.messaging.EmitterFactory;
import io.smallrye.reactive.messaging.MediatorConfiguration;
import io.smallrye.reactive.messaging.MessagePublisherProvider;
import io.smallrye.reactive.messaging.SubscriberDecorator;
import io.smallrye.reactive.messaging.annotations.EmitterFactoryFor;
import io.smallrye.reactive.messaging.annotations.Merge;
import io.smallrye.reactive.messaging.providers.AbstractMediator;
import io.smallrye.reactive.messaging.providers.extension.*;
import io.smallrye.reactive.messaging.providers.helpers.MultiUtils;
import io.smallrye.reactive.messaging.providers.i18n.ProviderLogging;

@ApplicationScoped
public class Wiring {

    public static final int DEFAULT_BUFFER_SIZE = 128;

    @Inject
    @ConfigProperty(name = "mp.messaging.emitter.default-buffer-size", defaultValue = "128")
    int defaultBufferSize;

    @Inject
    @ConfigProperty(name = "smallrye.messaging.emitter.default-buffer-size", defaultValue = "128")
    @Deprecated // Use mp.messaging.emitter.default-buffer-size instead
    int defaultBufferSizeLegacy;

    @Inject
    MediatorManager manager;

    @Any
    @Inject
    Instance> emitterFactories;

    @Any
    @Inject
    Instance subscriberDecorators;

    private final List components;

    private Graph graph;

    private boolean strictMode;

    public Wiring() {
        components = new ArrayList<>();
    }

    @PreDestroy
    public void terminateAllComponents() {
        components.forEach(Component::terminate);
    }

    public void prepare(boolean strictMode, ChannelRegistry registry, List emitters,
            List channels,
            List mediators) {
        this.strictMode = strictMode;

        for (MediatorConfiguration mediator : mediators) {
            if (mediator.getOutgoing() != null && !mediator.getIncoming().isEmpty()) {
                components.add(new ProcessorMediatorComponent(manager, mediator));
            } else if (mediator.getOutgoing() != null) {
                components.add(new PublisherMediatorComponent(manager, mediator));
            } else {
                components.add(new SubscriberMediatorComponent(manager, mediator));
            }
        }

        for (ChannelConfiguration channel : channels) {
            components.add(new InjectedChannelComponent(channel, strictMode));
        }

        for (EmitterConfiguration emitter : emitters) {
            components.add(new EmitterComponent(emitter, emitterFactories, defaultBufferSize, defaultBufferSizeLegacy));
        }

        // At that point, the registry only contains connectors or managed channels
        for (Map.Entry entry : registry.getIncomingChannels().entrySet()) {
            components.add(new InboundConnectorComponent(entry.getKey(), entry.getValue()));
        }

        for (Map.Entry entry : registry.getOutgoingChannels().entrySet()) {
            components.add(new OutgoingConnectorComponent(entry.getKey(), subscriberDecorators, entry.getValue()));
        }
    }

    public Graph resolve() {
        ProviderLogging.log.startGraphResolution(components.size());
        long begin = System.nanoTime();
        Set resolved = new LinkedHashSet<>();
        Set unresolved = new LinkedHashSet<>();

        // Initialize lists
        for (Component component : components) {
            if (component.isUpstreamResolved()) {
                resolved.add(component);
            } else {
                unresolved.add((ConsumingComponent) component);
            }
        }

        boolean doneOrStale = false;
        // Until everything is resolved or we got staled
        while (!doneOrStale) {
            List resolvedDuringThisTurn = new ArrayList<>();
            for (ConsumingComponent component : unresolved) {
                List incomings = component.incomings();
                for (String incoming : incomings) {
                    List matches = getMatchesFor(incoming, resolved);
                    if (!matches.isEmpty()) {
                        matches.forEach(m -> bind(component, m));
                        if (component.isUpstreamResolved()) {
                            resolvedDuringThisTurn.add(component);
                        }
                    }
                }
            }

            resolved.addAll(resolvedDuringThisTurn);
            resolvedDuringThisTurn.forEach(unresolved::remove);

            doneOrStale = resolvedDuringThisTurn.isEmpty() || unresolved.isEmpty();

            // Update components consuming multiple incomings.
            for (Component component : resolved) {
                if (component instanceof ConsumingComponent) {
                    ConsumingComponent cc = (ConsumingComponent) component;
                    List incomings = cc.incomings();
                    for (String incoming : incomings) {
                        List matches = getMatchesFor(incoming, resolved);
                        for (Component match : matches) {
                            bind(cc, match);
                        }
                    }
                }
            }
        }

        // Attempt to resolve from the unresolved set.
        List newlyResolved = new ArrayList<>();
        for (ConsumingComponent c : unresolved) {
            for (String incoming : c.incomings()) {
                // searched in unresolved
                List matches = getMatchesFor(incoming, unresolved);
                if (!matches.isEmpty()) {
                    newlyResolved.add(c);
                    matches.forEach(m -> bind(c, m));
                }
            }
        }
        if (!newlyResolved.isEmpty()) {
            newlyResolved.forEach(unresolved::remove);
            resolved.addAll(newlyResolved);
        }

        graph = new Graph(strictMode, resolved, unresolved);
        long duration = System.nanoTime() - begin;
        ProviderLogging.log.completedGraphResolution(duration);
        return graph;

    }

    public Graph getGraph() {
        return graph;
    }

    private void bind(ConsumingComponent consumer, Component provider) {
        consumer.connectUpstream(provider);
        provider.connectDownstream(consumer);
    }

    private List getMatchesFor(String incoming, Set candidates) {
        List matches = new ArrayList<>();
        for (Component component : candidates) {
            Optional outgoing = component.outgoing();
            if (outgoing.isPresent() && outgoing.get().equalsIgnoreCase(incoming)) {
                matches.add(component);
            }
        }
        return matches;
    }

    public interface Component {

        void validate() throws WiringException;

        boolean isUpstreamResolved();

        boolean isDownstreamResolved();

        default Optional outgoing() {
            return Optional.empty();
        }

        default List incomings() {
            return Collections.emptyList();
        }

        default Set downstreams() {
            return Collections.emptySet();
        }

        default Set upstreams() {
            return Collections.emptySet();
        }

        default void connectDownstream(Component downstream) {
            throw new UnsupportedOperationException("Downstream connection not expected for " + this);
        }

        void materialize(ChannelRegistry registry);

        default void terminate() {
            // do nothing by default
        }
    }

    public interface PublishingComponent extends Component {
        boolean broadcast();

        int getRequiredNumberOfSubscribers();

        default String getOutgoingChannel() {
            return outgoing().orElseThrow(() -> new IllegalStateException("Outgoing not configured for " + this));
        }

        @Override
        default boolean isDownstreamResolved() {
            return !downstreams().isEmpty();
        }

        @Override
        default void connectDownstream(Component downstream) {
            downstreams().add(downstream);
        }
    }

    public interface ConsumingComponent extends Component {

        @Override
        default boolean isUpstreamResolved() {
            return !upstreams().isEmpty();
        }

        default void connectUpstream(Component upstream) {
            upstreams().add(upstream);
        }

        boolean merge();
    }

    interface NoUpstreamComponent extends Component {
        @Override
        default boolean isUpstreamResolved() {
            return true;
        }
    }

    interface NoDownstreamComponent extends Component {
        @Override
        default boolean isDownstreamResolved() {
            return true;
        }
    }

    static class InboundConnectorComponent implements PublishingComponent, NoUpstreamComponent {

        private final String name;
        private final boolean broadcast;
        private final Set downstreams = new LinkedHashSet<>();

        public InboundConnectorComponent(String name, boolean broadcast) {
            this.name = name;
            this.broadcast = broadcast;
        }

        @Override
        public Optional outgoing() {
            return Optional.of(name);
        }

        @Override
        public Set downstreams() {
            return downstreams;
        }

        @Override
        public void materialize(ChannelRegistry registry) {
            // We are already registered and created.
        }

        @Override
        public boolean broadcast() {
            return broadcast;
        }

        @Override
        public int getRequiredNumberOfSubscribers() {
            return 0;
        }

        @Override
        public String toString() {
            return "IncomingConnector{channel:'" + name + "', attribute:'mp.messaging.incoming." + name + "'}";
        }

        @Override
        public void validate() throws WiringException {
            if (!broadcast && downstreams().size() > 1) {
                throw new TooManyDownstreamCandidatesException(this);
            }
        }
    }

    static class OutgoingConnectorComponent implements ConsumingComponent, NoDownstreamComponent {

        private final String name;
        private final Set upstreams = new LinkedHashSet<>();
        private final Instance subscriberDecorators;
        private final boolean merge;

        public OutgoingConnectorComponent(String name, Instance subscriberDecorators, boolean merge) {
            this.name = name;
            this.subscriberDecorators = subscriberDecorators;
            this.merge = merge;
        }

        @Override
        public List incomings() {
            return Collections.singletonList(name);
        }

        @Override
        public boolean merge() {
            return merge;
        }

        @Override
        public void connectUpstream(Component upstream) {
            upstreams.add(upstream);
        }

        @Override
        public Set upstreams() {
            return upstreams;
        }

        @Override
        public String toString() {
            return "OutgoingConnector{channel:'" + name + "', attribute:'mp.messaging.outgoing." + name + "'}";
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        @Override
        public void materialize(ChannelRegistry registry) {
            List>> publishers = registry.getPublishers(name);
            Multi> merged;
            if (publishers.size() == 1) {
                merged = MultiUtils.publisher(publishers.get(0));
            } else {
                merged = Multi.createBy().merging().streams(publishers.stream().map(p -> p).collect(Collectors.toList()));
            }
            // TODO Improve this.
            Flow.Subscriber connector = registry.getSubscribers(name).get(0);
            for (SubscriberDecorator decorator : getSortedInstances(subscriberDecorators)) {
                merged = decorator.decorate(merged, Collections.singletonList(name), true);
            }
            // The connector will cancel the subscription.
            merged.subscribe().withSubscriber(connector);
        }

        @Override
        public void validate() throws WiringException {
            if (upstreams().size() > 1 && !merge) {
                throw new TooManyUpstreamCandidatesException(this);
            }
        }
    }

    static class InjectedChannelComponent implements ConsumingComponent, NoDownstreamComponent {

        private final String name;
        private final Set upstreams = new LinkedHashSet<>();
        private final boolean strict;

        public InjectedChannelComponent(ChannelConfiguration configuration, boolean strict) {
            this.name = configuration.channelName;
            this.strict = strict;
        }

        @Override
        public List incomings() {
            return Collections.singletonList(name);
        }

        @Override
        public boolean merge() {
            return !strict;
        }

        @Override
        public Set upstreams() {
            return upstreams;
        }

        @Override
        public String toString() {
            return "@Channel{channel:'" + name + "'}";
        }

        @Override
        public void materialize(ChannelRegistry registry) {
            // Nothing to be done for channel - look up happen during the subscription.
        }

        @Override
        public void validate() throws WiringException {
            if (strict && upstreams().size() > 1) {
                throw new TooManyUpstreamCandidatesException(this);
            }
        }
    }

    static class EmitterComponent implements PublishingComponent, NoUpstreamComponent {

        private final EmitterConfiguration configuration;
        private final Instance> emitterFactories;
        private final Set downstreams = new LinkedHashSet<>();
        private final int defaultBufferSize;
        private final int defaultBufferSizeLegacy;

        public EmitterComponent(EmitterConfiguration configuration, Instance> emitterFactories,
                int defaultBufferSize,
                int defaultBufferSizeLegacy) {
            this.configuration = configuration;
            this.emitterFactories = emitterFactories;
            this.defaultBufferSize = defaultBufferSize;
            this.defaultBufferSizeLegacy = defaultBufferSizeLegacy;
        }

        @Override
        public Optional outgoing() {
            return Optional.of(configuration.name());
        }

        @Override
        public Set downstreams() {
            return downstreams;
        }

        @Override
        public boolean broadcast() {
            return configuration.broadcast();
        }

        @Override
        public int getRequiredNumberOfSubscribers() {
            return configuration.numberOfSubscriberBeforeConnecting();
        }

        @Override
        public String toString() {
            return "Emitter{channel:'" + getOutgoingChannel() + "'}";
        }

        @Override
        public void materialize(ChannelRegistry registry) {
            int def = getDefaultBufferSize();
            registerEmitter(registry, def);
        }

        private > void registerEmitter(ChannelRegistry registry, int def) {
            EmitterFactory emitterFactory = getEmitterFactory(configuration.emitterType());
            T emitter = (T) emitterFactory.createEmitter(configuration, def);
            Publisher> publisher = emitter.getPublisher();
            Class type = (Class) configuration.emitterType().value();
            registry.register(configuration.name(), type, emitter);
            //noinspection ReactiveStreamsUnusedPublisher
            registry.register(configuration.name(), publisher, broadcast());
        }

        private EmitterFactory getEmitterFactory(EmitterFactoryFor emitterType) {
            return emitterFactories.select(emitterType).get();
        }

        private int getDefaultBufferSize() {
            if (defaultBufferSize == DEFAULT_BUFFER_SIZE && defaultBufferSizeLegacy != DEFAULT_BUFFER_SIZE) {
                return defaultBufferSizeLegacy;
            } else {
                return defaultBufferSize;
            }
        }

        @Override
        public void validate() throws WiringException {
            if (!configuration.broadcast() && downstreams().size() > 1) {
                throw new TooManyDownstreamCandidatesException(this);
            }

            if (broadcast()
                    && getRequiredNumberOfSubscribers() != 0 && getRequiredNumberOfSubscribers() != downstreams.size()) {
                throw new UnsatisfiedBroadcastException(this);
            }
        }
    }

    abstract static class MediatorComponent implements Component {
        final MediatorConfiguration configuration;
        final MediatorManager manager;

        protected MediatorComponent(MediatorManager manager, MediatorConfiguration configuration) {
            this.configuration = configuration;
            this.manager = manager;
        }
    }

    static class PublisherMediatorComponent extends MediatorComponent implements PublishingComponent, NoUpstreamComponent {

        private final Set downstreams = new LinkedHashSet<>();
        private AbstractMediator mediator;

        protected PublisherMediatorComponent(MediatorManager manager, MediatorConfiguration configuration) {
            super(manager, configuration);
        }

        @Override
        public Optional outgoing() {
            return Optional.of(configuration.getOutgoing());
        }

        @Override
        public Set downstreams() {
            return downstreams;
        }

        @Override
        public void materialize(ChannelRegistry registry) {
            synchronized (this) {
                mediator = manager.createMediator(configuration);
            }
            registry.register(configuration.getOutgoing(), mediator.getStream(), broadcast());
        }

        @Override
        public boolean broadcast() {
            return configuration.getBroadcast();
        }

        @Override
        public int getRequiredNumberOfSubscribers() {
            return configuration.getNumberOfSubscriberBeforeConnecting();
        }

        @Override
        public String toString() {
            return "PublisherMethod{" +
                    "method:'" + configuration.methodAsString() + "', outgoing:'" + getOutgoingChannel() + "'}";
        }

        @Override
        public void validate() throws WiringException {
            if (!broadcast() && downstreams().size() > 1) {
                throw new TooManyDownstreamCandidatesException(this);
            }
            if (broadcast()
                    && getRequiredNumberOfSubscribers() != 0 && getRequiredNumberOfSubscribers() != downstreams.size()) {
                throw new UnsatisfiedBroadcastException(this);
            }
        }

        @Override
        public synchronized void terminate() {
            if (mediator != null) {
                mediator.terminate();
            }
        }
    }

    static class SubscriberMediatorComponent extends MediatorComponent implements ConsumingComponent, NoDownstreamComponent {

        private final Set upstreams = new LinkedHashSet<>();

        private AbstractMediator mediator;

        protected SubscriberMediatorComponent(MediatorManager manager, MediatorConfiguration configuration) {
            super(manager, configuration);
        }

        @Override
        public Set upstreams() {
            return upstreams;
        }

        @Override
        public List incomings() {
            return configuration.getIncoming();
        }

        @Override
        public boolean merge() {
            return configuration.getMerge() != null;
        }

        @Override
        public void materialize(ChannelRegistry registry) {
            synchronized (this) {
                mediator = manager.createMediator(configuration);
            }

            boolean concat = configuration.getMerge() == Merge.Mode.CONCAT;
            boolean one = configuration.getMerge() == Merge.Mode.ONE;

            Multi> aggregates;
            List>> publishers = new ArrayList<>();
            for (String channel : configuration.getIncoming()) {
                publishers.addAll(registry.getPublishers(channel));
            }

            if (publishers.size() == 1) {
                aggregates = MultiUtils.publisher(publishers.get(0));
            } else if (concat) {
                aggregates = Multi.createBy().concatenating()
                        .streams(publishers.stream().map(p -> p).collect(Collectors.toList()));
            } else if (one) {
                aggregates = MultiUtils.publisher(publishers.get(0));
            } else {
                aggregates = Multi.createBy().merging()
                        .streams(publishers.stream().map(p -> p).collect(Collectors.toList()));
            }

            mediator.connectToUpstream(aggregates);

            Flow.Subscriber> subscriber = mediator.getComputedSubscriber();
            incomings().forEach(s -> registry.register(s, subscriber, merge()));

            mediator.run();
        }

        @Override
        public String toString() {
            return "SubscriberMethod{" +
                    "method:'" + configuration.methodAsString() + "', incoming:'" + String
                            .join(",", configuration.getIncoming())
                    + "'}";
        }

        private boolean hasAllUpstreams() {
            // A subscriber can have multiple incomings - all of them must be bound.
            for (String incoming : incomings()) {
                // For each incoming, check that we have a match
                if (upstreams().stream().noneMatch(c -> incoming.equals(c.outgoing().orElse(null)))) {
                    return false;
                }
            }

            return true;
        }

        @Override
        public boolean isUpstreamResolved() {
            return hasAllUpstreams();
        }

        @Override
        public void validate() throws WiringException {
            // Check that for each incoming we have a single upstream or a merge strategy
            for (String incoming : incomings()) {
                List components = downstreams().stream()
                        .filter(c -> incoming.equals(c.outgoing().orElse(null)))
                        .collect(Collectors.toList());
                if (components.size() > 1 && !merge()) {
                    throw new TooManyUpstreamCandidatesException(this, incoming, components);
                }
            }

            if (!merge() && upstreams.size() != incomings().size()) {
                throw new TooManyUpstreamCandidatesException(this);
            }
        }

        @Override
        public synchronized void terminate() {
            if (mediator != null) {
                mediator.terminate();
            }
        }
    }

    static class ProcessorMediatorComponent extends MediatorComponent
            implements ConsumingComponent, PublishingComponent {

        private final Set upstreams = new LinkedHashSet<>();
        private final Set downstreams = new LinkedHashSet<>();
        private AbstractMediator mediator;

        protected ProcessorMediatorComponent(MediatorManager manager, MediatorConfiguration configuration) {
            super(manager, configuration);
        }

        @Override
        public Set upstreams() {
            return upstreams;
        }

        @Override
        public List incomings() {
            return configuration.getIncoming();
        }

        @Override
        public boolean merge() {
            return configuration.getMerge() != null;
        }

        @Override
        public Optional outgoing() {
            return Optional.of(configuration.getOutgoing());
        }

        @Override
        public Set downstreams() {
            return downstreams;
        }

        @Override
        public boolean broadcast() {
            return configuration.getBroadcast();
        }

        @Override
        public int getRequiredNumberOfSubscribers() {
            return configuration.getNumberOfSubscriberBeforeConnecting();
        }

        private boolean hasAllUpstreams() {
            // A subscriber can have multiple incomings - all of them must be bound.
            for (String incoming : incomings()) {
                // For each incoming, check that we have a match
                if (upstreams().stream().noneMatch(c -> incoming.equals(c.outgoing().orElse(null)))) {
                    return false;
                }
            }
            return true;
        }

        @Override
        public boolean isUpstreamResolved() {
            return hasAllUpstreams();
        }

        @Override
        public String toString() {
            return "ProcessingMethod{" +
                    "method:'" + configuration.methodAsString()
                    + "', incoming:'" + String.join(",", configuration.getIncoming())
                    + "', outgoing:'" + getOutgoingChannel() + "'}";
        }

        @Override
        public void materialize(ChannelRegistry registry) {
            synchronized (this) {
                mediator = manager.createMediator(configuration);
            }

            boolean concat = configuration.getMerge() == Merge.Mode.CONCAT;
            boolean one = configuration.getMerge() == Merge.Mode.ONE;

            Multi> aggregates;
            List>> publishers = new ArrayList<>();
            for (String channel : configuration.getIncoming()) {
                publishers.addAll(registry.getPublishers(channel));
            }
            if (publishers.size() == 1) {
                aggregates = MultiUtils.publisher(publishers.get(0));
            } else if (concat) {
                aggregates = Multi.createBy().concatenating()
                        .streams(publishers.stream().map(p -> p).collect(Collectors.toList()));
            } else if (one) {
                aggregates = MultiUtils.publisher(publishers.get(0));
            } else {
                aggregates = Multi.createBy().merging()
                        .streams(publishers.stream().map(p -> p).collect(Collectors.toList()));
            }

            mediator.connectToUpstream(aggregates);

            registry.register(getOutgoingChannel(), mediator.getStream(), merge());
        }

        @Override
        public void validate() throws WiringException {
            // Check that for each incoming we have a single upstream or a merge strategy
            for (String incoming : incomings()) {
                List components = downstreams().stream()
                        .filter(c -> incoming.equals(c.outgoing().orElse(null)))
                        .collect(Collectors.toList());
                if (components.size() > 1 && !merge()) {
                    throw new TooManyUpstreamCandidatesException(this, incoming, components);
                }
            }

            if (!merge() && upstreams.size() != incomings().size()) {
                throw new TooManyUpstreamCandidatesException(this);
            }

            if (!broadcast() && downstreams().size() > 1) {
                throw new TooManyDownstreamCandidatesException(this);
            }

            if (broadcast()
                    && getRequiredNumberOfSubscribers() != 0 && getRequiredNumberOfSubscribers() != downstreams.size()) {
                throw new UnsatisfiedBroadcastException(this);
            }
        }

        @Override
        public synchronized void terminate() {
            if (mediator != null) {
                mediator.terminate();
            }
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy