ConfiguredCdiBeanBinder.java

package io.extact.rms.platform.extension;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.StreamSupport;

import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.context.Dependent;
import jakarta.enterprise.context.RequestScoped;

import org.eclipse.microprofile.config.Config;

public class ConfiguredCdiBeanBinder {

    private static final Map<String, Annotation> BEAN_SCOPES = Map.of(
            "application", ApplicationScoped.Literal.INSTANCE,
            "request", RequestScoped.Literal.INSTANCE,
            "dependent", Dependent.Literal.INSTANCE);

    private Config config;
    private String registerPrefix;
    private List<AliasKey> aliasKeys;


    // ----------------------------------------------------- factory methods

    public static ConfiguredCdiBeanBinder newBinder(Config config) {
        var configBinder = new ConfiguredCdiBeanBinder();
        configBinder.config = config;
        return configBinder;
    }

    public static ConfiguredCdiBeansListBinder newListBinder(Config config) {
        return ConfiguredCdiBeanBinder.newBinder(config).new ConfiguredCdiBeansListBinder();
    }


    // ----------------------------------------------------- public methods

    public ConfiguredCdiBeanBindFinisher key(String regsiterPrefix) {
        this.registerPrefix = regsiterPrefix;
        return new ConfiguredCdiBeanBindFinisher();
    }

    public ConfiguredCdiBeanBinder alias(String aliasPrefixKey) {
        this.aliasKeys = seekAliasKeys(aliasPrefixKey);
        return this;
    }

    private ConfiguredCdiBeanBinder aliasKeys(List<AliasKey> aliasKeys) {
        this.aliasKeys = aliasKeys;
        return this;
    }


    // ----------------------------------------------------- private methods

    private List<AliasKey> seekAliasKeys(String prefix) {
        // (config)
        // a.b.c.x.class = Foo
        // a.b.c.x.scope = application
        // a.b.c.y.tyepe = Bar
        // a.b.c.y.scope = request
        // ---------------------------
        // keyPrefix => "a.b.c"
        // map       => ["a.b.c.x", "a.b.c.x", "a.b.c.y", "a.b.c.y"]
        // distinct  => ["a.b.c.x", "a.b.c.y"]
        // map       => [{"x":"a.b.c.x"}, {"y":"a.b.c.y"}]
        //
        int endPosOfPrefix = prefix.length() + 1;
        return StreamSupport.stream(config.getPropertyNames().spliterator(), false)
                .filter(s -> s.startsWith(prefix))
                .map(s -> s.substring(0, endPosOfPrefix + s.indexOf('.', endPosOfPrefix) - (endPosOfPrefix)))
                .distinct()
                .map(s -> new AliasKey(s.substring(endPosOfPrefix), s))
                .toList();
    }


    // ----------------------------------------------------- inner class(Binder)

    public class ConfiguredCdiBeanBindFinisher {

        private ConfiguredCdiBeanBindFinisher() {
            // nop
        }

        public ConfiguredCdiBean bind() {

            Optional<String> alias = config.getOptionalValue(registerPrefix + ".alias", String.class);
            if (alias.isPresent()) {
                if (aliasKeys == null) {
                    throw new IllegalStateException("alias() not called!");
                }
                AliasKey aliasKey = aliasKeys.stream()
                        .filter(key -> alias.get().equals(key.aliasName))
                        .findFirst()
                        .orElseThrow(() -> new IllegalArgumentException("alias is unmatch. [" + alias.get() + "]"));
                return ConfiguredCdiBeanBinder.newBinder(config).key(aliasKey.aliasConfigKey).bind();
            }

            var configCdiBean = new ConfiguredCdiBean();
            configCdiBean.beanClass = config.getOptionalValue(registerPrefix + ".class", Class.class)
                    .orElseThrow(() -> new IllegalArgumentException("class is required. [" + registerPrefix + ".class]")); // class is required.

            String scopeValue = config.getOptionalValue(registerPrefix + ".scope", String.class).orElse("application"); // applicaiton is default.
            configCdiBean.scope = Optional.ofNullable(BEAN_SCOPES.get(scopeValue))
                    .orElseThrow(() -> new IllegalArgumentException("unknown scoped value. [" + registerPrefix + ".scope:" + scopeValue + "]"));

            configCdiBean.id = config.getOptionalValue(registerPrefix + ".id", String.class).orElse(configCdiBean.beanClass.getName()); // FQCN is default.

            return configCdiBean;
        }
    }


    // ----------------------------------------------------- inner classes(ListBinder)

    public class ConfiguredCdiBeansListBinder {

        private ConfiguredCdiBeansListBinder() {
            // nop
        }

        private String listAliasPrefix;
        private String listRegisterPrefix;

        public ConfiguredCdiBeansListBinder alias(String listAliasPrefix) {
            this.listAliasPrefix = listAliasPrefix;
            return this;
        }

        public ConfiguredCdiBeansListBindFinisher key(String listRegisterPrefix) {
            this.listRegisterPrefix = listRegisterPrefix;
            return new ConfiguredCdiBeansListBindFinisher(this);
        }

    }

    public class ConfiguredCdiBeansListBindFinisher {

        private ConfiguredCdiBeansListBinder parent;

        private ConfiguredCdiBeansListBindFinisher(ConfiguredCdiBeansListBinder listBinder) {
            this.parent = listBinder;
        }

        public List<ConfiguredCdiBean> bind() {

            List<AliasKey> foundAliasKeys = List.of();
            if (parent.listAliasPrefix != null) {
                foundAliasKeys = seekAliasKeys(parent.listAliasPrefix);
            }

            // (by single config)
            // a.b.c.class = Foo
            // a.b.c.scope = application
            //
            Optional<String> clazz = config.getOptionalValue(parent.listRegisterPrefix + ".class", String.class);
            Optional<String> alias = config.getOptionalValue(parent.listRegisterPrefix + ".alias", String.class);
            if (clazz.isPresent() || alias.isPresent()) {
                ConfiguredCdiBean configCdiBean =
                        ConfiguredCdiBeanBinder.newBinder(config)
                            .aliasKeys(foundAliasKeys)
                            .key(parent.listRegisterPrefix)
                            .bind();
                return List.of(configCdiBean);
            }

            // (by list config)
            // a.b.c.0.class = Foo
            // a.b.c.0.scope = application
            // a.b.c.1.tyepe = Bar
            // a.b.c.1.scope = request
            // ---------------------------
            // keyPrefix => "a.b.c"
            // map       => ["a.b.c.0", "a.b.c.0", "a.b.c.1", "a.b.c.1"]
            // distinct  => ["a.b.c.0", "a.b.c.1"]
            //
            int endPosOfPrefix = parent.listRegisterPrefix.length() + 1;
            List<String> keys = StreamSupport.stream(config.getPropertyNames().spliterator(), false)
                    .filter(s -> s.startsWith(parent.listRegisterPrefix))
                    .map(s -> s.substring(0, endPosOfPrefix + s.indexOf('.', endPosOfPrefix) - endPosOfPrefix))
                    .distinct()
                    .toList();

            List<ConfiguredCdiBean> configCdiBeans = new ArrayList<>();
            for (int i = 0; i < keys.size(); i++) {
                configCdiBeans.add(ConfiguredCdiBeanBinder.newBinder(config).aliasKeys(foundAliasKeys).key(parent.listRegisterPrefix + "." + i).bind());
            }
            return configCdiBeans;
        }
    }


    // ----------------------------------------------------- inner classes(AliasKey)

    static class AliasKey {
        private String aliasName;
        private String aliasConfigKey;
        private AliasKey(String aliasName, String aliasConfigKey) {
            this.aliasName = aliasName;
            this.aliasConfigKey = aliasConfigKey;
        }
    }
}