EntandoCompositeAppSpecFluent.java

/*
 *
 * Copyright 2015-Present Entando Inc. (http://www.entando.com) All rights reserved.
 *
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 *  This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 */

package org.entando.kubernetes.model.compositeapp;

import io.fabric8.kubernetes.api.builder.Builder;
import io.fabric8.kubernetes.api.builder.Nested;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.entando.kubernetes.model.app.EntandoApp;
import org.entando.kubernetes.model.app.EntandoAppBuilder;
import org.entando.kubernetes.model.app.EntandoAppFluent;
import org.entando.kubernetes.model.common.DbmsVendor;
import org.entando.kubernetes.model.common.EntandoBaseCustomResource;
import org.entando.kubernetes.model.common.EntandoCustomResourceStatus;
import org.entando.kubernetes.model.common.EntandoFluent;
import org.entando.kubernetes.model.debundle.EntandoDeBundle;
import org.entando.kubernetes.model.debundle.EntandoDeBundleBuilder;
import org.entando.kubernetes.model.externaldatabase.EntandoDatabaseService;
import org.entando.kubernetes.model.externaldatabase.EntandoDatabaseServiceBuilder;
import org.entando.kubernetes.model.externaldatabase.EntandoDatabaseServiceFluent;
import org.entando.kubernetes.model.keycloakserver.EntandoKeycloakServer;
import org.entando.kubernetes.model.keycloakserver.EntandoKeycloakServerBuilder;
import org.entando.kubernetes.model.keycloakserver.EntandoKeycloakServerFluent;
import org.entando.kubernetes.model.link.EntandoAppPluginLink;
import org.entando.kubernetes.model.link.EntandoAppPluginLinkBuilder;
import org.entando.kubernetes.model.link.EntandoAppPluginLinkFluent;
import org.entando.kubernetes.model.plugin.EntandoPlugin;
import org.entando.kubernetes.model.plugin.EntandoPluginBuilder;
import org.entando.kubernetes.model.plugin.EntandoPluginFluent;

//Sonar gets confused with generics within generics in return types
@SuppressWarnings("java:S1452")
public abstract class EntandoCompositeAppSpecFluent<F extends EntandoCompositeAppSpecFluent<F>> {

    private static final Map<Class<? extends EntandoBaseCustomResource<? extends Serializable, EntandoCustomResourceStatus>>,
            Class<? extends EntandoFluent<?>>> BUILDERS = createBuilderMap();
    protected List<EntandoFluent<?>> components;
    private String ingressHostNameOverride;
    private DbmsVendor dbmsOverride;
    private String tlsSecretNameOverride;

    protected EntandoCompositeAppSpecFluent(EntandoCompositeAppSpec spec) {
        this.components = createComponentBuilders(spec.getComponents());
        this.ingressHostNameOverride = spec.getIngressHostNameOverride().orElse(null);
        this.dbmsOverride = spec.getDbmsOverride().orElse(null);
        this.tlsSecretNameOverride = spec.getTlsSecretNameOverride().orElse(null);
    }

    protected EntandoCompositeAppSpecFluent() {
        this.components = new ArrayList<>();
    }

    public static EntandoFluent<?> newBuilderFrom(EntandoBaseCustomResource<? extends Serializable, EntandoCustomResourceStatus> r) {
        try {
            return BUILDERS.get(r.getClass()).getConstructor(r.getClass()).newInstance(r);
        } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
            throw new IllegalStateException(e);
        }
    }

    private static Map<Class<? extends EntandoBaseCustomResource<? extends Serializable, EntandoCustomResourceStatus>>,
            Class<? extends EntandoFluent<?>>> createBuilderMap() {
        Map<Class<? extends EntandoBaseCustomResource<? extends Serializable, EntandoCustomResourceStatus>>,
                Class<? extends EntandoFluent<?>>> result = new ConcurrentHashMap<>();
        result.put(EntandoKeycloakServer.class, EntandoKeycloakServerBuilder.class);
        result.put(EntandoApp.class, EntandoAppBuilder.class);
        result.put(EntandoPlugin.class, EntandoPluginBuilder.class);
        result.put(EntandoAppPluginLink.class, EntandoAppPluginLinkBuilder.class);
        result.put(EntandoDatabaseService.class, EntandoDatabaseServiceBuilder.class);
        result.put(EntandoDeBundle.class, EntandoDeBundleBuilder.class);
        result.put(EntandoCustomResourceReference.class, EntandoCustomResourceReferenceBuilder.class);
        return result;
    }

    public final F withDbmsOverride(DbmsVendor dbmsOverride) {
        this.dbmsOverride = dbmsOverride;
        return thisAsF();
    }

    public final F withTlsSecretNameOverride(String tlsSecretNameOverride) {
        this.tlsSecretNameOverride = tlsSecretNameOverride;
        return thisAsF();
    }

    public final F withIngressHostNameOverride(String ingressHostNameOverride) {
        this.ingressHostNameOverride = ingressHostNameOverride;
        return thisAsF();
    }

    public F withComponents(List<EntandoBaseCustomResource<? extends Serializable, EntandoCustomResourceStatus>> components) {
        this.components = createComponentBuilders(components);
        return thisAsF();
    }

    @SafeVarargs
    public final F withComponents(EntandoBaseCustomResource<? extends Serializable, EntandoCustomResourceStatus>... components) {
        return withComponents(Arrays.asList(components));
    }

    private List<EntandoFluent<?>> createComponentBuilders(
            List<EntandoBaseCustomResource<? extends Serializable, EntandoCustomResourceStatus>> components) {
        return components.stream()
                .map(EntandoCompositeAppSpecFluent::newBuilderFrom).collect(Collectors.<EntandoFluent<?>>toList());
    }

    @SuppressWarnings("unchecked")
    protected F thisAsF() {
        return (F) this;
    }

    //Sonar's solution gives compilation errors
    @SuppressWarnings({"java:S1612", "unchecked"})
    public EntandoCompositeAppSpec build() {
        return new EntandoCompositeAppSpec(this.components.stream()
                .map(Builder.class::cast)
                .map(Builder::build)
                .map(o -> (EntandoBaseCustomResource<?, EntandoCustomResourceStatus>) o)
                .collect(Collectors.toList()), ingressHostNameOverride, dbmsOverride, tlsSecretNameOverride);
    }

    public EntandoKeycloakServerNested<F> addNewEntandoKeycloakServer() {
        return new EntandoKeycloakServerNested<>(thisAsF());
    }

    public F addToEntandoKeycloakServers(EntandoKeycloakServer item) {
        this.components.add(new EntandoKeycloakServerBuilder(item));
        return thisAsF();
    }

    public EntandoAppNested<F> addNewEntandoApp() {
        return new EntandoAppNested<>(thisAsF());
    }

    public F addToEntandoApps(EntandoApp item) {
        this.components.add(new EntandoAppBuilder(item));
        return thisAsF();
    }

    public EntandoPluginNested<F> addNewEntandoPlugin() {
        return new EntandoPluginNested<>(thisAsF());
    }

    public F addToEntandoPlugins(EntandoPlugin item) {
        this.components.add(new EntandoPluginBuilder(item));
        return thisAsF();
    }

    public EntandoCustomResourceReferenceNested<F> addNewEntandoCustomResourceReference() {
        return new EntandoCustomResourceReferenceNested<>(thisAsF());
    }

    public F addToEntandoCustomResourceReferences(EntandoCustomResourceReference item) {
        this.components.add(new EntandoCustomResourceReferenceBuilder(item));
        return thisAsF();
    }

    public EntandoAppPluginLinkNested<F> addNewEntandoAppPluginLink() {
        return new EntandoAppPluginLinkNested<>(thisAsF());
    }

    public F addToEntandoAppPluginLinks(EntandoAppPluginLink item) {
        this.components.add(new EntandoAppPluginLinkBuilder(item));
        return thisAsF();
    }

    public EntandoDatabaseServiceNested<F> addNewEntandoDatabaseService() {
        return new EntandoDatabaseServiceNested<>(thisAsF());
    }

    public F addToEntandoDatabaseServices(EntandoDatabaseService item) {
        this.components.add(new EntandoDatabaseServiceBuilder(item));
        return thisAsF();
    }

    public static class EntandoKeycloakServerNested<N extends EntandoCompositeAppSpecFluent<N>> extends
            EntandoKeycloakServerFluent<EntandoKeycloakServerNested<N>> implements Nested<N> {

        private final N parentBuilder;

        public EntandoKeycloakServerNested(N parentBuilder) {
            super();
            this.parentBuilder = parentBuilder;
        }

        public N and() {
            return parentBuilder.addToEntandoKeycloakServers(new EntandoKeycloakServer(super.metadata.build(), super.spec.build()));
        }

        public N endEntandoKeycloakServer() {
            return and();
        }
    }

    public static class EntandoAppNested<N extends EntandoCompositeAppSpecFluent<N>> extends
            EntandoAppFluent<EntandoAppNested<N>> implements Nested<N> {

        private final N parentBuilder;

        public EntandoAppNested(N parentBuilder) {
            super();
            this.parentBuilder = parentBuilder;
        }

        public N and() {
            return parentBuilder
                    .addToEntandoApps(new EntandoApp(super.metadata.build(), super.spec.build()));
        }

        public N endEntandoApp() {
            return and();
        }
    }

    public static class EntandoPluginNested<N extends EntandoCompositeAppSpecFluent<N>> extends
            EntandoPluginFluent<EntandoPluginNested<N>> implements Nested<N> {

        private final N parentBuilder;

        public EntandoPluginNested(N parentBuilder) {
            super();
            this.parentBuilder = parentBuilder;
        }

        public N and() {
            return parentBuilder
                    .addToEntandoPlugins(new EntandoPlugin(super.metadata.build(), super.spec.build()));
        }

        public N endEntandoPlugin() {
            return and();
        }
    }

    public static class EntandoAppPluginLinkNested<N extends EntandoCompositeAppSpecFluent<N>> extends
            EntandoAppPluginLinkFluent<EntandoAppPluginLinkNested<N>> implements Nested<N> {

        private final N parentBuilder;

        public EntandoAppPluginLinkNested(N parentBuilder) {
            super();
            this.parentBuilder = parentBuilder;
        }

        public N and() {
            return parentBuilder
                    .addToEntandoAppPluginLinks(new EntandoAppPluginLink(super.metadata.build(), super.spec.build()));
        }

        public N endEntandoAppPluginLink() {
            return and();
        }
    }

    public static class EntandoDatabaseServiceNested<N extends EntandoCompositeAppSpecFluent<N>> extends
            EntandoDatabaseServiceFluent<EntandoDatabaseServiceNested<N>> implements Nested<N> {

        private final N parentBuilder;

        public EntandoDatabaseServiceNested(N parentBuilder) {
            super();
            this.parentBuilder = parentBuilder;
        }

        public N and() {
            return parentBuilder
                    .addToEntandoDatabaseServices(new EntandoDatabaseService(super.metadata.build(), super.spec.build()));
        }

        public N endEntandoDatabaseService() {
            return and();
        }
    }

    public static class EntandoCustomResourceReferenceNested<N extends EntandoCompositeAppSpecFluent<N>> extends
            EntandoCustomResourceReferenceFluent<EntandoCustomResourceReferenceNested<N>> implements Nested<N> {

        private final N parentBuilder;

        public EntandoCustomResourceReferenceNested(N parentBuilder) {
            super();
            this.parentBuilder = parentBuilder;
        }

        public N and() {
            return parentBuilder
                    .addToEntandoCustomResourceReferences(new EntandoCustomResourceReference(super.metadata.build(), super.spec.build()));
        }

        public N endEntandoCustomResourceReference() {
            return and();
        }
    }

}