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

io.smallrye.reactive.messaging.providers.impl.ConfiguredChannelFactory Maven / Gradle / Ivy

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

import static io.smallrye.reactive.messaging.providers.helpers.CDIUtils.getSortedInstances;
import static io.smallrye.reactive.messaging.providers.i18n.ProviderExceptions.ex;
import static io.smallrye.reactive.messaging.providers.i18n.ProviderLogging.log;

import java.util.*;
import java.util.concurrent.Flow;

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

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.reactive.messaging.Message;
import org.eclipse.microprofile.reactive.messaging.spi.*;

import io.smallrye.mutiny.Multi;
import io.smallrye.reactive.messaging.ChannelRegistar;
import io.smallrye.reactive.messaging.ChannelRegistry;
import io.smallrye.reactive.messaging.PublisherDecorator;
import io.smallrye.reactive.messaging.connector.InboundConnector;
import io.smallrye.reactive.messaging.connector.OutboundConnector;
import io.smallrye.reactive.messaging.providers.helpers.MultiUtils;

/**
 * Look for stream factories and get instances.
 */
@ApplicationScoped
public class ConfiguredChannelFactory implements ChannelRegistar {

    protected final Config config;
    protected final ChannelRegistry registry;
    private final ConnectorFactories factories;

    @Inject
    Instance publisherDecoratorInstance;

    // CDI requirement for normal scoped beans
    protected ConfiguredChannelFactory() {
        this.config = null;
        this.registry = null;
        this.factories = null;
    }

    @Inject
    public ConfiguredChannelFactory(ConnectorFactories factories,
            Instance config,
            @Any Instance registry) {

        this(factories, config, registry, true);
    }

    ConfiguredChannelFactory(ConnectorFactories factories,
            Instance config, @Any Instance registry,
            boolean logConnectors) {
        this.registry = registry.get();
        this.factories = factories;
        if (config.isUnsatisfied()) {
            this.config = null;
        } else {
            if (logConnectors) {
                log.foundIncomingConnectors(factories.getInboundConnectors().keySet());
                log.foundOutgoingConnectors(factories.getOutboundConnectors().keySet());
            }
            this.config = config.stream().findFirst()
                    .orElseThrow(ex::illegalStateRetrieveConfig);
        }
    }

    static Map extractConfigurationFor(String prefix, Config root) {
        Iterable names = root.getPropertyNames();
        Map configs = new HashMap<>();
        names.forEach(key -> {
            // $prefix$name.key=value (the prefix ends with a .)
            if (key.startsWith(prefix)) {
                // Extract the name
                String name = key.substring(prefix.length());
                if (name.charAt(0) == '"') { // Check if the name is enclosed by double quotes
                    name = name.substring(1, name.lastIndexOf('"'));
                } else if (name.contains(".")) { // We must remove the part after the first dot
                    String tmp = name;
                    name = tmp.substring(0, tmp.indexOf('.'));
                }
                configs.put(name, new ConnectorConfig(prefix, root, name));
            }
        });
        return configs;
    }

    @Override
    public void initialize() {
        if (this.config == null) {
            log.skippingMPConfig();
            return;
        }

        log.channelManagerInitializing();

        Map sourceConfiguration = extractConfigurationFor(ConnectorFactory.INCOMING_PREFIX, config);
        Map sinkConfiguration = extractConfigurationFor(ConnectorFactory.OUTGOING_PREFIX, config);

        detectNameConflict(sourceConfiguration, sinkConfiguration);

        register(sourceConfiguration, sinkConfiguration);
    }

    /**
     * By spec, you cannot use the same channel name in an `incoming` configuration and `outgoing` configuration.
     * This method throws a {@link jakarta.enterprise.inject.spi.DeploymentException} is this case is detected.
     *
     * @param sourceConfiguration the source configurations
     * @param sinkConfiguration the sink configurations
     */
    private void detectNameConflict(Map sourceConfiguration,
            Map sinkConfiguration) {
        // We must create a copy as removing the items from the set remove them from the map.
        Set sources = new HashSet<>(sourceConfiguration.keySet());
        Set sinks = sinkConfiguration.keySet();
        sources.retainAll(sinks);
        if (!sources.isEmpty()) {
            throw ex.deploymentInvalidConfiguration(sources);
        }

    }

    void register(Map incomings, Map outgoings) {
        try {
            for (Map.Entry entry : incomings.entrySet()) {
                String channel = entry.getKey();
                ConnectorConfig config = entry.getValue();
                if (config.getOptionalValue(ConnectorConfig.CHANNEL_ENABLED_PROPERTY, Boolean.TYPE).orElse(true)) {
                    registry.register(channel, createPublisher(channel, config),
                            config.getOptionalValue(ConnectorConfig.BROADCAST_PROPERTY, Boolean.class).orElse(false));
                } else {
                    log.incomingChannelDisabled(channel);
                }
            }

            for (Map.Entry entry : outgoings.entrySet()) {
                String channel = entry.getKey();
                ConnectorConfig config = entry.getValue();
                if (config.getOptionalValue(ConnectorConfig.CHANNEL_ENABLED_PROPERTY, Boolean.TYPE).orElse(true)) {
                    registry.register(channel, createSubscriber(channel, config),
                            config.getOptionalValue(ConnectorConfig.MERGE_PROPERTY, Boolean.class).orElse(false));
                } else {
                    log.outgoingChannelDisabled(channel);
                }
            }
        } catch (RuntimeException e) { // NOSONAR
            log.unableToCreatePublisherOrSubscriber(e);
            throw e;
        }
    }

    private static String getConnectorAttribute(Config config) {
        // This method looks for connector and type.
        // The availability has been checked when the config object has been created
        return config.getValue("connector", String.class);
    }

    private Flow.Publisher> createPublisher(String name, Config config) {
        // Extract the type and throw an exception if missing
        String connector = getConnectorAttribute(config);

        InboundConnector inboundConnector = factories.getInboundConnectors().get(connector);
        if (inboundConnector == null) {
            throw ex.illegalArgumentUnknownConnector(name);
        }

        Multi> publisher = MultiUtils.publisher(inboundConnector.getPublisher(config));

        for (PublisherDecorator decorator : getSortedInstances(publisherDecoratorInstance)) {
            publisher = decorator.decorate(publisher, name, true);
        }

        return publisher;
    }

    private Flow.Subscriber> createSubscriber(String name, Config config) {
        // Extract the type and throw an exception if missing
        String connector = getConnectorAttribute(config);

        OutboundConnector outboundConnector = factories.getOutboundConnectors().get(connector);
        if (outboundConnector == null) {
            throw ex.illegalArgumentUnknownConnector(name);
        }

        return outboundConnector.getSubscriber(config);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy