-
Notifications
You must be signed in to change notification settings - Fork 2
Feature/281 plugin system to choose storagemanager implementation #301
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
abdessamad-abdoun
merged 8 commits into
feature/corese-next
from
feature/281-plugin-system-to-choose-storagemanager-implementation
Mar 18, 2026
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
2bd9d79
Plugin system to choose StorageManager implementation
abdessamad-abdoun 9183d87
Plugin system to choose StorageManager implementation
abdessamad-abdoun 5395d78
revue Plugin system to choose StorageManager implementation
abdessamad-abdoun 8134ce2
revue Plugin system to choose StorageManager implementation
abdessamad-abdoun 047d03b
revue Plugin system to choose StorageManager implementation
abdessamad-abdoun 70c3f4b
Plugin system to choose StorageManager implementation
abdessamad-abdoun 0f74e4f
Plugin system to choose StorageManager implementation
abdessamad-abdoun eed6db0
Plugin system to choose StorageManager implementation
abdessamad-abdoun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
172 changes: 172 additions & 0 deletions
172
src/main/java/fr/inria/corese/core/next/data/factory/ModelFactory.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| package fr.inria.corese.core.next.data.factory; | ||
|
|
||
| import fr.inria.corese.core.Graph; | ||
| import fr.inria.corese.core.next.data.api.Model; | ||
| import fr.inria.corese.core.next.data.api.ValueFactory; | ||
| import fr.inria.corese.core.next.data.impl.StorageModel; | ||
| import fr.inria.corese.core.next.storagemanager.api.plugin.PluginException; | ||
| import fr.inria.corese.core.next.storagemanager.api.plugin.StoragePluginManager; | ||
| import fr.inria.corese.core.next.storagemanager.api.support.config.StorageConfig; | ||
|
|
||
| /** | ||
| * Factory class for creating Model instances with different storage backends. | ||
| * | ||
| * <p>This factory simplifies the creation of models by providing convenient methods | ||
| * to create models backed by different storage implementations (Memory, Graph, etc.) | ||
| * without requiring explicit StorageManager configuration. | ||
| * | ||
| * <h2>Supported Storage Types</h2> | ||
| * <ul> | ||
| * <li><b>"memory"</b> - In-memory HashMap-based storage (fast, no persistence)</li> | ||
| * <li><b>"graph"</b> - Legacy Graph-based storage (indexed, persistent)</li> | ||
| * </ul> | ||
| * | ||
| * @see Model | ||
| * @see StorageModel | ||
| * @see StoragePluginManager | ||
| * @see ValueFactory | ||
| */ | ||
| public record ModelFactory(ValueFactory valueFactory) { | ||
|
|
||
| /** | ||
| * Constructs a new ModelFactory with the specified ValueFactory. | ||
| * | ||
| * @param valueFactory the ValueFactory to use for creating RDF values | ||
| * @throws NullPointerException if valueFactory is null | ||
| */ | ||
| public ModelFactory { | ||
| if (valueFactory == null) { | ||
| throw new NullPointerException("ValueFactory cannot be null"); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new Model instance with the specified storage backend type. | ||
| * | ||
| * @param storageType the storage backend type (case-insensitive) | ||
| * @return a new Model instance backed by the specified storage type | ||
| * @throws IllegalArgumentException if storageType is null or empty | ||
| * @throws PluginException if the storage backend fails to initialize | ||
| * @see #createModel(StorageConfig) | ||
| */ | ||
| public Model createModel(String storageType) throws PluginException { | ||
| if (storageType == null || storageType.trim().isEmpty()) { | ||
| throw new IllegalArgumentException("Storage type cannot be null or empty"); | ||
| } | ||
|
|
||
| // Create minimal config with just the type | ||
| // Let StoragePluginManager discover and select the appropriate plugin | ||
| StorageConfig config = StorageConfig.builder() | ||
| .property("type", storageType.trim()) | ||
| .build(); | ||
|
|
||
| return createModel(config); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new Model instance from a {@link StorageConfig}. | ||
| * | ||
| * <p>This is the most flexible method for creating models, allowing full control | ||
| * over storage configuration. The {@link StoragePluginManager} will select the | ||
| * appropriate plugin based on the config.</p> | ||
| * | ||
| * @param config the storage configuration (must not be null) | ||
| * @return a new Model instance backed by the configured storage | ||
| * @throws IllegalArgumentException if config is null | ||
| * @throws PluginException if the storage backend fails to initialize | ||
| * @see #createModel(String) | ||
| */ | ||
| public Model createModel(StorageConfig config) throws PluginException { | ||
| if (config == null) { | ||
| throw new IllegalArgumentException("StorageConfig cannot be null"); | ||
| } | ||
|
|
||
| return StorageModel.builder() | ||
| .storage(StoragePluginManager.create(config)) | ||
| .valueFactory(valueFactory) | ||
| .build(); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new memory-based Model instance. | ||
| * | ||
| * <p>Memory storage uses a ConcurrentHashMap backend for fast, in-memory storage. | ||
| * This is ideal for testing, prototyping, and small datasets (< 100K statements). | ||
| * | ||
| * @return a new Model instance backed by memory storage | ||
| * @throws PluginException if the memory storage fails to initialize | ||
| * @see #createGraphModel() | ||
| * @see #createModel(String) | ||
| */ | ||
| public Model createMemoryModel() throws PluginException { | ||
| return createModel(createMemoryConfig()); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new graph-based Model instance. | ||
| * | ||
| * <p>Graph storage wraps the legacy Corese Graph implementation, providing | ||
| * indexed access, persistence support, and excellent performance for large datasets. | ||
| * This is the recommended choice for production use. | ||
| * | ||
| * @return a new Model instance backed by a new Graph storage | ||
| * @throws PluginException if the graph storage fails to initialize | ||
| * @see #createGraphModel(Graph) | ||
| * @see #createMemoryModel() | ||
| * @see #createModel(String) | ||
| */ | ||
| public Model createGraphModel() throws PluginException { | ||
| return createModel(createGraphConfig()); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a new graph-based Model instance backed by the specified Graph. | ||
| * | ||
| * <p>This method allows you to wrap an existing Graph instance with the Model API, | ||
| * enabling you to use legacy Graph-based code with the new Model interface. | ||
| * All operations on the returned Model will delegate to the underlying Graph. | ||
| * | ||
| * @param graph the Graph instance to wrap with the Model API | ||
| * @return a new Model instance backed by the specified Graph | ||
| * @throws NullPointerException if graph is null | ||
| * @throws PluginException if the graph storage fails to initialize | ||
| * @see #createGraphModel() | ||
| */ | ||
| public Model createGraphModel(Graph graph) throws PluginException { | ||
| if (graph == null) { | ||
| throw new NullPointerException("Graph cannot be null"); | ||
| } | ||
|
|
||
| StorageConfig config = StorageConfig.builder() | ||
| .property("type", "graph") | ||
| .property("graph", graph) | ||
| .property("valueFactory", valueFactory) | ||
| .build(); | ||
|
|
||
| return createModel(config); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a StorageConfig for memory-based storage. | ||
| * | ||
| * @return a StorageConfig configured for memory storage | ||
| */ | ||
| private StorageConfig createMemoryConfig() { | ||
| return StorageConfig.builder() | ||
| .property("type", "memory") | ||
| .build(); | ||
| } | ||
|
|
||
| /** | ||
| * Creates a StorageConfig for graph-based storage with a new Graph instance. | ||
| * | ||
| * @return a StorageConfig configured for graph storage | ||
| */ | ||
| private StorageConfig createGraphConfig() { | ||
| return StorageConfig.builder() | ||
| .property("type", "graph") | ||
| .property("graph", Graph.create()) | ||
| .property("valueFactory", valueFactory) | ||
| .build(); | ||
| } | ||
| } |
95 changes: 95 additions & 0 deletions
95
src/main/java/fr/inria/corese/core/next/storagemanager/api/plugin/ExternalPluginLoader.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| package fr.inria.corese.core.next.storagemanager.api.plugin; | ||
|
|
||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| import java.io.File; | ||
| import java.net.URL; | ||
| import java.net.URLClassLoader; | ||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
| import java.util.ServiceLoader; | ||
|
|
||
| /** | ||
| * Utility class for loading StoragePlugin instances from external JAR files. | ||
| */ | ||
| public class ExternalPluginLoader { | ||
|
|
||
| private static final Logger logger = LoggerFactory.getLogger(ExternalPluginLoader.class); | ||
|
|
||
| /** | ||
| * List of loaded plugin class loaders (to prevent garbage collection). | ||
| */ | ||
| private static final List<ClassLoader> loadedClassLoaders = new ArrayList<>(); | ||
|
|
||
| /** | ||
| * Private constructor - this is a utility class. | ||
| */ | ||
| private ExternalPluginLoader() { | ||
| throw new AssertionError("ExternalPluginLoader is a utility class"); | ||
| } | ||
|
|
||
| /** | ||
| * Loads all plugins from a specific JAR file. | ||
| * | ||
| * @param jarFile the JAR file containing plugins | ||
| * @return the number of plugins loaded from this JAR | ||
| * @throws IllegalArgumentException if jarFile is null or doesn't exist | ||
| * @throws Exception if loading fails | ||
| */ | ||
| public static int loadPluginsFromJar(File jarFile) throws Exception { | ||
| if (jarFile == null) { | ||
| throw new IllegalArgumentException("JAR file cannot be null"); | ||
| } | ||
| if (!jarFile.exists() || !jarFile.isFile()) { | ||
| throw new IllegalArgumentException("JAR file does not exist: " + jarFile); | ||
| } | ||
|
|
||
| logger.info("Loading plugins from JAR: {} ({} bytes)", | ||
| jarFile.getName(), jarFile.length()); | ||
|
|
||
| // Create ClassLoader for the JAR | ||
| URLClassLoader classLoader = new URLClassLoader( | ||
| new URL[]{jarFile.toURI().toURL()}, | ||
| ExternalPluginLoader.class.getClassLoader() | ||
| ); | ||
|
|
||
| // Keep reference to prevent garbage collection | ||
| loadedClassLoaders.add(classLoader); | ||
|
|
||
| // Register ClassLoader with StoragePluginManager | ||
| StoragePluginManager.registerClassLoader(classLoader); | ||
|
|
||
| // Load plugins using ServiceLoader | ||
| ServiceLoader<StoragePlugin> loader = ServiceLoader.load( | ||
| StoragePlugin.class, | ||
| classLoader | ||
| ); | ||
|
|
||
| int count = 0; | ||
| for (StoragePlugin plugin : loader) { | ||
| logger.info("Loaded plugin: {} (priority={}, jar={})", | ||
| plugin.getName(), | ||
| plugin.getPriority(), | ||
| jarFile.getName()); | ||
| count++; | ||
| } | ||
|
|
||
| // Refresh the plugin cache | ||
| if (count > 0) { | ||
| StoragePluginManager.reload(); | ||
| logger.info("Plugin cache refreshed"); | ||
| } | ||
|
|
||
| return count; | ||
| } | ||
|
|
||
|
|
||
| /** | ||
| * Clears all loaded class loaders. | ||
| */ | ||
| public static void clear() { | ||
| logger.info("Clearing {} loaded class loaders", loadedClassLoaders.size()); | ||
| loadedClassLoaders.clear(); | ||
| } | ||
| } |
18 changes: 18 additions & 0 deletions
18
src/main/java/fr/inria/corese/core/next/storagemanager/api/plugin/PluginException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package fr.inria.corese.core.next.storagemanager.api.plugin; | ||
|
|
||
| import fr.inria.corese.core.next.storagemanager.api.support.exception.ErrorCode; | ||
| import fr.inria.corese.core.next.storagemanager.api.support.exception.StorageException; | ||
|
|
||
| /** | ||
| * Exception thrown when a plugin fails to create a StorageManager instance. | ||
| */ | ||
| public class PluginException extends StorageException { | ||
|
|
||
| public PluginException(String message) { | ||
| super(ErrorCode.PLUGIN_CREATION_FAILED, message); | ||
| } | ||
|
|
||
| public PluginException(String message, Throwable cause) { | ||
| super(ErrorCode.PLUGIN_CREATION_FAILED, message, cause); | ||
| } | ||
| } |
11 changes: 11 additions & 0 deletions
11
...ain/java/fr/inria/corese/core/next/storagemanager/api/plugin/PluginNotFoundException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package fr.inria.corese.core.next.storagemanager.api.plugin; | ||
|
|
||
| /** | ||
| * Exception thrown when no plugin is found for the requested configuration. | ||
| */ | ||
| public class PluginNotFoundException extends PluginException { | ||
|
|
||
| public PluginNotFoundException(String message) { | ||
| super(message); | ||
| } | ||
| } |
80 changes: 80 additions & 0 deletions
80
src/main/java/fr/inria/corese/core/next/storagemanager/api/plugin/StoragePlugin.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| package fr.inria.corese.core.next.storagemanager.api.plugin; | ||
|
|
||
| import fr.inria.corese.core.next.storagemanager.api.StorageManager; | ||
| import fr.inria.corese.core.next.storagemanager.api.support.config.StorageConfig; | ||
|
|
||
| /** | ||
| * Service Provider Interface (SPI) for StorageManager plugins. | ||
| */ | ||
| public interface StoragePlugin { | ||
|
|
||
| /** | ||
| * Returns the unique name of this plugin. | ||
| * | ||
| * @return the plugin name (must be unique and non-null) | ||
| */ | ||
| String getName(); | ||
|
|
||
| /** | ||
| * Returns a human-readable description of this plugin. | ||
| * | ||
| * @return the plugin description (never null) | ||
| */ | ||
| default String getDescription() { | ||
| return "StorageManager plugin: " + getName(); | ||
| } | ||
|
|
||
| /** | ||
| * Checks if this plugin supports the given configuration. | ||
| * | ||
| * | ||
| * @param config the storage configuration to check (never null) | ||
| * @return {@code true} if this plugin can create a StorageManager for this config | ||
| * @throws IllegalArgumentException if config is null | ||
| */ | ||
| boolean supports(StorageConfig config); | ||
|
|
||
| /** | ||
| * Creates a StorageManager instance from the given configuration. | ||
| * | ||
| * @param config the storage configuration (never null) | ||
| * @return a configured StorageManager instance (never null) | ||
| * @throws PluginException if the StorageManager cannot be created | ||
| * @throws IllegalArgumentException if config is null | ||
| */ | ||
| StorageManager create(StorageConfig config) throws PluginException; | ||
|
|
||
| /** | ||
| * Returns the priority of this plugin. | ||
| * | ||
| * <p>When multiple plugins support the same configuration (i.e., their | ||
| * {@link #supports(StorageConfig)} method returns {@code true}), the plugin | ||
| * with the <b>highest priority</b> is selected. | ||
| * | ||
| * <p><b>Priority Semantics:</b> | ||
| * <ul> | ||
| * <li><b>Higher values = Higher priority:</b> A plugin with priority 100 will be | ||
| * selected over one with priority 50</li> | ||
| * <li><b>Default is 0:</b> If this method is not overridden, the plugin has priority 0</li> | ||
| * <li><b>Negative values are allowed:</b> Use negative priorities for fallback plugins | ||
| * that should only be used when no other plugin is available</li> | ||
| * </ul> | ||
| * | ||
| * | ||
| * <p><b>Built-in Priorities:</b> | ||
| * <ul> | ||
| * <li>GraphStoragePlugin: 100</li> | ||
| * <li>MemoryStoragePlugin: 50</li> | ||
| * </ul> | ||
| * | ||
| * | ||
| * <p><b>Tie-Breaking:</b> If multiple plugins have the same priority and support | ||
| * the same configuration, the selection is non-deterministic. Avoid this by ensuring | ||
| * each plugin has a unique priority for overlapping configurations. | ||
| * | ||
| * @return the plugin priority (higher values = higher priority, default is 0) | ||
| */ | ||
| default int getPriority() { | ||
| return 0; | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add a comment in the class description about the intended usage of priority ?