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

org.sentrysoftware.metricshub.engine.extension.ExtensionLoader Maven / Gradle / Ivy

There is a newer version: 0.9.06
Show newest version
package org.sentrysoftware.metricshub.engine.extension;

/*-
 * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
 * MetricsHub Engine
 * ჻჻჻჻჻჻
 * Copyright 2023 - 2024 Sentry Software
 * ჻჻჻჻჻჻
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
 */

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.ServiceLoader;
import java.util.ServiceLoader.Provider;
import java.util.jar.JarFile;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.sentrysoftware.metricshub.classloader.agent.ClassLoaderAgent;

/**
 * Manages the loading of extensions from a specified directory and produces an {@link ExtensionManager}.
 * This class is responsible for finding, loading, and initializing extensions that extend the functionality
 * of MetricsHub. The extensions are expected to be jar files located in the specified extensions directory.
 */
@Data
@RequiredArgsConstructor
@Slf4j
public class ExtensionLoader {

	@NonNull
	private File extensionsDirectory;

	private ExtensionManager extensionManager;

	/**
	 * Loads extensions from the {@code extensionsDirectory} and returns an {@link ExtensionManager} that wraps
	 * all the loaded extensions. Extensions are loaded as JAR files and are expected to implement certain
	 * extension interfaces to be recognized and used by the system.
	 *
	 * @return An {@link ExtensionManager} containing all loaded extensions.
	 * @throws IOException If an I/O error occurs reading from the directory or a JAR file.
	 */
	public ExtensionManager load() throws IOException {
		if (extensionManager != null) {
			return extensionManager;
		}

		// Get the jar files located under the extension directory
		final File[] extensionJars = extensionsDirectory.listFiles((unused, fileName) -> fileName.endsWith(".jar"));

		// If there is no extension then create an empty extension manger and skip the current loading
		if (extensionJars == null || extensionJars.length == 0) {
			log.debug("No extension to load from {}. Stop extension loading.", extensionsDirectory);
			return ExtensionManager.empty();
		}

		// Create JAR URLs
		final URL[] urls = new URL[extensionJars.length];
		for (int i = 0; i < extensionJars.length; i++) {
			final File jarFile = extensionJars[i];

			// Make sure to add the jar to the system class loader search
			ClassLoaderAgent.addToClassPath(new JarFile(jarFile));
			urls[i] = jarFile.toURI().toURL();
		}

		// Create the class loader
		final URLClassLoader classLoader = new URLClassLoader(urls);

		// Load protocol extensions
		final ServiceLoader protocolExtensions = ServiceLoader.load(
			IProtocolExtension.class,
			classLoader
		);
		protocolExtensions.forEach(extension ->
			log.info("Loaded protocol extension {}.", extension.getClass().getSimpleName())
		);

		// Load strategy provider extensions
		final ServiceLoader strategyProviderExtensions = ServiceLoader.load(
			IStrategyProviderExtension.class,
			classLoader
		);
		strategyProviderExtensions.forEach(extension ->
			log.info("Loaded strategy provider extension {}.", extension.getClass().getSimpleName())
		);

		// Load connector store provider extensions
		final ServiceLoader connectorStoreProviderExtensions = ServiceLoader.load(
			IConnectorStoreProviderExtension.class,
			classLoader
		);
		connectorStoreProviderExtensions.forEach(extension ->
			log.info("Loaded connector store provider extension {}.", extension.getClass().getSimpleName())
		);

		// Load source computation extensions
		final ServiceLoader sourceComputationExtensions = ServiceLoader.load(
			ISourceComputationExtension.class,
			classLoader
		);
		sourceComputationExtensions.forEach(extension ->
			log.info("Loaded source computation extension {}.", extension.getClass().getSimpleName())
		);

		// Load Jawk extensions
		final ServiceLoader compositeSourceScriptExtensions = ServiceLoader.load(
			ICompositeSourceScriptExtension.class,
			classLoader
		);
		compositeSourceScriptExtensions.forEach(extension ->
			log.info("Loaded composite source script extension {}.", extension.getClass().getSimpleName())
		);

		// Build the extension manager
		extensionManager =
			ExtensionManager
				.builder()
				.withProtocolExtensions(convertProviderStreamToList(protocolExtensions.stream()))
				.withStrategyProviderExtensions(convertProviderStreamToList(strategyProviderExtensions.stream()))
				.withConnectorStoreProviderExtensions(convertProviderStreamToList(connectorStoreProviderExtensions.stream()))
				.withSourceComputationExtensions(convertProviderStreamToList(sourceComputationExtensions.stream()))
				.withCompositeSourceScriptExtensions(convertProviderStreamToList(compositeSourceScriptExtensions.stream()))
				.build();

		return extensionManager;
	}

	/**
	 * Converts a stream of {@link ServiceLoader.Provider} objects into a list of their extension instances.
	 *
	 * @param  The type of the extension interface.
	 * @param providerStream The stream of {@link ServiceLoader.Provider} objects to convert.
	 * @return A list of extension instances provided by the input stream.
	 */
	 List convertProviderStreamToList(final Stream> providerStream) {
		return providerStream.map(Provider::get).collect(Collectors.toCollection(ArrayList::new));
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy