EnvironmentResolverConfigurationMapCreator.java

/*-
 * ========================LICENSE_START=================================
 * flyway-core
 * ========================================================================
 * Copyright (C) 2010 - 2026 Red Gate Software Ltd
 * ========================================================================
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * =========================LICENSE_END==================================
 */
package org.flywaydb.core.internal.configuration.resolvers;

import java.util.Map;
import java.util.stream.Collectors;
import org.flywaydb.core.api.CoreErrorCode;
import org.flywaydb.core.api.FlywayException;
import org.flywaydb.core.extensibility.ConfigurationExtension;
import org.flywaydb.core.extensibility.Plugin;
import org.flywaydb.core.internal.configuration.models.EnvironmentModel;
import org.flywaydb.core.internal.plugin.PluginRegister;
import tools.jackson.core.JacksonException.Reference;
import tools.jackson.databind.DeserializationFeature;
import tools.jackson.databind.ObjectMapper;
import tools.jackson.databind.exc.MismatchedInputException;

public class EnvironmentResolverConfigurationMapCreator {

    public Map<String, ConfigurationExtension> createMap(final EnvironmentModel environmentModel,
        final PluginRegister pluginRegister) {

        if (environmentModel.getResolvers() != null) {
            return environmentModel.getResolvers()
                .keySet()
                .stream()
                .collect(Collectors.toMap(key -> key, v -> getResolverConfig(environmentModel, pluginRegister, v)));
        }
        return null;
    }

    private ConfigurationExtension getResolverConfig(final EnvironmentModel environmentModel,
        final PluginRegister pluginRegister,
        final String key) {
        final Class<?> clazz = getResolverConfigClassFromKey(pluginRegister, key);

        if (clazz != null) {
            try {
                final var data = environmentModel.getResolvers().get(key);
                return (ConfigurationExtension) new ObjectMapper().rebuild()
                    .configure(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES, false)
                    .build()
                    .convertValue(data, clazz);
            } catch (final MismatchedInputException e) {
                //noinspection ThrowInsideCatchBlockWhichIgnoresCaughtException
                throw new FlywayException("Error reading resolver configuration for resolver "
                    + key
                    + ". Expected type '"
                    + e.getTargetType().getSimpleName()
                    + "' for "
                    + e.getPath().stream().map(Reference::getPropertyName).collect(Collectors.joining(".")),
                    CoreErrorCode.CONFIGURATION);
            } catch (final IllegalArgumentException e) {
                throw new FlywayException("Error reading resolver configuration for resolver " + key,
                    e,
                    CoreErrorCode.CONFIGURATION);
            }
        }

        throw new FlywayException("Unable to find resolver: " + key);
    }

    private Class<? extends Plugin> getResolverClassFromKey(final PluginRegister pluginRegister, final String key) {
        Plugin plugin = pluginRegister.getInstancesOf(EnvironmentProvisioner.class)
            .stream()
            .filter(p -> matchesNameOrAlias(p, key))
            .findFirst()
            .orElse(null);

        if (plugin == null) {
            plugin = pluginRegister.getInstancesOf(PropertyResolver.class)
                .stream()
                .filter(p -> matchesNameOrAlias(p, key))
                .findFirst()
                .orElse(null);
        }

        if (plugin != null) {
            return plugin.getClass();
        }

        throw new FlywayException("Unable to find resolver: " + key);
    }

    private boolean matchesNameOrAlias(final Plugin plugin, final String key) {
        if (plugin.getName().equalsIgnoreCase(key)) {
            return true;
        }

        if (plugin instanceof final PropertyResolver resolver) {
            return resolver.getAliases().stream().anyMatch(alias -> alias.equalsIgnoreCase(key));
        }

        return false;
    }

    @SuppressWarnings("ChainOfInstanceofChecks")
    private Class<?> getResolverConfigClassFromKey(final PluginRegister pluginRegister, final String key) {
        final Class<? extends Plugin> resolverClass = getResolverClassFromKey(pluginRegister, key);
        if (resolverClass == null) {
            return null;
        }

        final Plugin plugin = pluginRegister.getExact(resolverClass);
        if (plugin instanceof final EnvironmentProvisioner environmentProvisioner) {
            return environmentProvisioner.getConfigClass();
        }
        if (plugin instanceof final PropertyResolver propertyResolver) {
            return propertyResolver.getConfigClass();
        }

        throw new FlywayException("Unable to find resolver: " + key);
    }
}