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

org.finos.tracdap.common.plugin.PluginManager Maven / Gradle / Ivy

Go to download

TRAC D.A.P. common library, interfaces and utilities used across all TRAC components

There is a newer version: 0.6.3
Show newest version
/*
 * Licensed to the Fintech Open Source Foundation (FINOS) under one or
 * more contributor license agreements. See the NOTICE file distributed
 * with this work for additional information regarding copyright ownership.
 * FINOS licenses this file to you 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.
 */

package org.finos.tracdap.common.plugin;

import org.finos.tracdap.common.config.ConfigManager;
import org.finos.tracdap.common.exception.EPluginNotAvailable;
import org.finos.tracdap.common.exception.EUnexpected;
import org.finos.tracdap.common.startup.StartupLog;

import org.finos.tracdap.config.PluginConfig;
import org.slf4j.event.Level;

import java.util.*;
import java.util.stream.Collectors;


public class PluginManager implements IPluginManager {

    private static final List CONFIG_SERVICE_TYPES = List.of(
            PluginServiceInfo.CONFIG_SERVICE_TYPE,
            PluginServiceInfo.SECRETS_SERVICE_TYPE);

    private final Map plugins;

    public PluginManager() {
        plugins = new HashMap<>();
    }

    public void initConfigPlugins() {

        StartupLog.log(this, Level.INFO, "Loading config plugins...");

        var availablePlugins = ServiceLoader.load(ITracPlugin.class).iterator();

        while (availablePlugins.hasNext()) {

            try {

                // Handling to allow for plugins that fail to load due to missing dependencies
                // This happens in the sandbox, e.g. if -svc-meta detects a plugin from -svc-data
                // We could log warnings here, noisy in normal use but helpful if plugins are failing to load
                // Perhaps add a switch to turn on extra logging for plugin load?

                var plugin = availablePlugins.next();
                var services = plugin.serviceInfo();

                var configServices = services.stream()
                        .filter(si -> CONFIG_SERVICE_TYPES.contains(si.serviceType()))
                        .collect(Collectors.toList());

                if (!configServices.isEmpty()) {

                    StartupLog.log(this, Level.INFO, String.format("Plugin: [%s]", plugin.pluginName()));

                    registerServices(plugin, configServices);
                }
            }
            catch (ServiceConfigurationError e) {

                StartupLog.log(this, Level.WARN, e.getMessage());
            }
        }
    }

    public void initRegularPlugins() {

        StartupLog.log(this, Level.INFO, "Loading plugins...");

        var availablePlugins = ServiceLoader.load(ITracPlugin.class).iterator();

        while (availablePlugins.hasNext()) {

            try {

                var plugin = availablePlugins.next();
                var services = plugin.serviceInfo();

                var regularServices = services.stream()
                        .filter(si -> !CONFIG_SERVICE_TYPES.contains(si.serviceType()))
                        .collect(Collectors.toList());

                if (!regularServices.isEmpty()) {

                    StartupLog.log(this, Level.INFO, String.format("Plugin: [%s]", plugin.pluginName()));

                    registerServices(plugin, regularServices);
                }
            }
            catch (ServiceConfigurationError e) {

                StartupLog.log(this, Level.WARN, e.getMessage());
            }
        }
    }

    private void registerServices(ITracPlugin plugin, List services) {

        for (var service : services) {

            var prettyServiceType = prettyTypeName(service.serviceType(), true);
            var protocols = String.join(", ", service.protocols());


            StartupLog.log(this, Level.INFO, String.format(
                    " |-> %s: [%s] (protocols: %s)",
                    prettyServiceType, service.serviceName(), protocols));

            for (var protocol : service.protocols()) {

                var pluginKey = new PluginKey(service.serviceClass(), protocol);
                plugins.put(pluginKey, plugin);
            }
        }
    }

    @Override
    public List availableProtocols(Class serviceClass) {

        var protocols = new HashSet();

        for (var pluginKey : plugins.keySet()) {
            if (pluginKey.serviceClass() == serviceClass)
                protocols.add(pluginKey.protocol());
        }

        return new ArrayList<>(protocols);
    }

    @Override
    public boolean isServiceAvailable(Class serviceClass, String protocol) {

        var pluginKey = new PluginKey(serviceClass, protocol);
        return plugins.containsKey(pluginKey);
    }

    @Override
    public  T createService(Class serviceClass, PluginConfig pluginConfig, ConfigManager configManager) {

        var plugin = lookupPlugin(serviceClass, pluginConfig.getProtocol());

        return plugin.createService(serviceClass, pluginConfig, configManager);
    }

    @Override
    public  T createService(Class serviceClass, String protocol, ConfigManager configManager) {

        var pluginConfig = PluginConfig.newBuilder()
                .setProtocol(protocol)
                .build();

        return createService(serviceClass, pluginConfig, configManager);
    }

    @Override
    public  T createConfigService(Class serviceClass, PluginConfig pluginConfig) {

        var plugin = lookupPlugin(serviceClass, pluginConfig.getProtocol());

        return plugin.createConfigService(serviceClass, pluginConfig);
    }

    @Override
    public  T createConfigService(Class serviceClass, String protocol, Properties properties) {

        var pluginConfig = PluginConfig.newBuilder()
                .setProtocol(protocol);

        for (var property: properties.entrySet()) {
            var key = property.getKey().toString();
            var value = property.getValue().toString();
            pluginConfig.putProperties(key, value);
        }

        var plugin = lookupPlugin(serviceClass, protocol);

        return plugin.createConfigService(serviceClass, pluginConfig.build());
    }

    private  ITracPlugin lookupPlugin(Class serviceClass, String protocol) {

        var pluginKey = new PluginKey(serviceClass, protocol);

        if (!PluginServiceInfo.SERVICE_TYPES.containsKey(serviceClass.getName()))
            throw new EUnexpected();

        if (protocol.isBlank()) {

            var serviceType = PluginServiceInfo.SERVICE_TYPES.get(serviceClass.getName());
            var message = String.format("Protocol not specified for [%s] plugin", serviceType);

            StartupLog.log(this, Level.ERROR, message);
            throw new EPluginNotAvailable(message);
        }

        if (!plugins.containsKey(pluginKey)) {

            var rawTypeName = PluginServiceInfo.SERVICE_TYPES.get(serviceClass.getName());
            var message = String.format(
                    "Plugin not available for %s protocol: [%s]",
                    prettyTypeName(rawTypeName, false), protocol);

            StartupLog.log(this, Level.ERROR, message);
            throw new EPluginNotAvailable(message);
        }

        return plugins.get(pluginKey);
    }

    private String prettyTypeName(String rawTypeName, boolean caps) {

        var caseAndSpaces = rawTypeName
                .toLowerCase()
                .replace("_", " ")
                .replace("-", " ");

        if (caps) {
            var firstLetter = caseAndSpaces.substring(0, 1).toUpperCase();
            return firstLetter + caseAndSpaces.substring(1);
        }
        else
            return caseAndSpaces;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy