Skip to content

Commit fe40801

Browse files
Plugin system to choose StorageManager implementation
#281
1 parent fee0d27 commit fe40801

10 files changed

Lines changed: 447 additions & 6 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package fr.inria.corese.core.next.storagemanager.api.plugin;
2+
3+
import fr.inria.corese.core.next.storagemanager.api.support.exception.ErrorCode;
4+
import fr.inria.corese.core.next.storagemanager.api.support.exception.StorageException;
5+
6+
/**
7+
* Exception thrown when a plugin fails to create a StorageManager instance.
8+
*/
9+
public class PluginException extends StorageException {
10+
11+
public PluginException(String message) {
12+
super(ErrorCode.PLUGIN_CREATION_FAILED, message);
13+
}
14+
15+
public PluginException(String message, Throwable cause) {
16+
super(ErrorCode.PLUGIN_CREATION_FAILED, message, cause);
17+
}
18+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package fr.inria.corese.core.next.storagemanager.api.plugin;
2+
3+
/**
4+
* Exception thrown when no plugin is found for the requested configuration.
5+
*/
6+
public class PluginNotFoundException extends PluginException {
7+
8+
public PluginNotFoundException(String message) {
9+
super(message);
10+
}
11+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package fr.inria.corese.core.next.storagemanager.api.plugin;
2+
3+
import fr.inria.corese.core.next.storagemanager.api.StorageManager;
4+
import fr.inria.corese.core.next.storagemanager.api.support.config.StorageConfig;
5+
6+
/**
7+
* Service Provider Interface (SPI) for StorageManager plugins.
8+
*/
9+
public interface StoragePlugin {
10+
11+
/**
12+
* Returns the unique name of this plugin.
13+
*
14+
* @return the plugin name (must be unique and non-null)
15+
*/
16+
String getName();
17+
18+
/**
19+
* Returns a human-readable description of this plugin.
20+
*
21+
* @return the plugin description (never null)
22+
*/
23+
default String getDescription() {
24+
return "StorageManager plugin: " + getName();
25+
}
26+
27+
28+
29+
/**
30+
* Creates a StorageManager instance from the given configuration.
31+
*
32+
* @param config the storage configuration (never null)
33+
* @return a configured StorageManager instance (never null)
34+
* @throws PluginException if the StorageManager cannot be created
35+
* @throws IllegalArgumentException if config is null
36+
*/
37+
StorageManager create(StorageConfig config) throws PluginException;
38+
39+
/**
40+
* Returns the priority of this plugin.
41+
*
42+
* @return the plugin priority
43+
*/
44+
default int getPriority() {
45+
return 0;
46+
}
47+
48+
}
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
package fr.inria.corese.core.next.storagemanager.api.plugin;
2+
3+
import fr.inria.corese.core.next.storagemanager.api.StorageManager;
4+
import fr.inria.corese.core.next.storagemanager.api.support.config.StorageConfig;
5+
6+
import java.util.*;
7+
import java.util.concurrent.ConcurrentHashMap;
8+
import java.util.stream.Collectors;
9+
10+
/**
11+
* Manager for discovering and creating StorageManager plugins.
12+
*/
13+
public class StoragePluginManager {
14+
15+
/**
16+
* ServiceLoader for discovering plugins
17+
*/
18+
private static final ServiceLoader<StoragePlugin> serviceLoader =
19+
ServiceLoader.load(StoragePlugin.class);
20+
21+
/**
22+
* Cache of discovered plugins (thread-safe)
23+
*/
24+
private static volatile List<StoragePlugin> cachedPlugins;
25+
26+
/**
27+
* Plugin lookup cache by name (thread-safe)
28+
*/
29+
private static final Map<String, StoragePlugin> pluginsByName =
30+
new ConcurrentHashMap<>();
31+
32+
/**
33+
* Private constructor - this is a static utility class.
34+
*/
35+
private StoragePluginManager() {
36+
throw new AssertionError("StoragePluginManager is a utility class");
37+
}
38+
39+
/**
40+
* Creates a StorageManager instance from the given configuration.
41+
*
42+
* @param config the storage configuration (must not be null)
43+
* @return a configured StorageManager instance
44+
* @throws IllegalArgumentException if config is null
45+
* @throws PluginNotFoundException if no plugin supports the configuration
46+
* @throws PluginException if the StorageManager cannot be created
47+
*/
48+
public static StorageManager create(StorageConfig config) throws PluginException {
49+
if (config == null) {
50+
throw new IllegalArgumentException("StorageConfig must not be null");
51+
}
52+
53+
// Get all available plugins
54+
List<StoragePlugin> allPlugins = getAvailablePlugins();
55+
56+
// Find plugins that support this configuration
57+
List<StoragePlugin> supportingPlugins = allPlugins.stream()
58+
.sorted(Comparator.comparingInt(StoragePlugin::getPriority).reversed())
59+
.toList();
60+
61+
if (supportingPlugins.isEmpty()) {
62+
String availableTypes = allPlugins.stream()
63+
.map(StoragePlugin::getName)
64+
.collect(Collectors.joining(", "));
65+
66+
throw new PluginNotFoundException(
67+
String.format("No plugin found for storage type '%s'. Available types: [%s]",
68+
config.getType(), availableTypes)
69+
);
70+
}
71+
72+
// Select plugin with highest priority
73+
StoragePlugin selectedPlugin = supportingPlugins.getFirst();
74+
75+
// Log warning if multiple plugins match
76+
if (supportingPlugins.size() > 1) {
77+
String otherPlugins = supportingPlugins.stream()
78+
.skip(1)
79+
.map(p -> p.getName() + " (priority=" + p.getPriority() + ")")
80+
.collect(Collectors.joining(", "));
81+
82+
System.out.println("WARNING: Multiple plugins support this configuration. " +
83+
"Selected '" + selectedPlugin.getName() + "' (priority=" +
84+
selectedPlugin.getPriority() + "). Ignored: " + otherPlugins);
85+
}
86+
87+
// Create StorageManager
88+
try {
89+
return selectedPlugin.create(config);
90+
} catch (PluginException e) {
91+
throw e;
92+
} catch (Exception e) {
93+
throw new PluginException(
94+
"Failed to create StorageManager with plugin '" +
95+
selectedPlugin.getName() + "'", e);
96+
}
97+
}
98+
99+
/**
100+
* Returns all available StoragePlugin implementations.
101+
*
102+
* @return unmodifiable list of available plugins (never null, may be empty)
103+
*/
104+
public static List<StoragePlugin> getAvailablePlugins() {
105+
if (cachedPlugins == null) {
106+
synchronized (StoragePluginManager.class) {
107+
if (cachedPlugins == null) {
108+
List<StoragePlugin> plugins = new ArrayList<>();
109+
110+
// Reload ServiceLoader to discover new plugins
111+
serviceLoader.reload();
112+
113+
// Collect all plugins
114+
for (StoragePlugin plugin : serviceLoader) {
115+
plugins.add(plugin);
116+
pluginsByName.put(plugin.getName(), plugin);
117+
}
118+
119+
// Sort by priority (highest first)
120+
plugins.sort(Comparator.comparingInt(StoragePlugin::getPriority).reversed());
121+
122+
// Make immutable
123+
cachedPlugins = Collections.unmodifiableList(plugins);
124+
}
125+
}
126+
}
127+
128+
return cachedPlugins;
129+
}
130+
131+
/**
132+
* Finds a plugin by its name.
133+
*
134+
* @param name the plugin name (case-sensitive)
135+
* @return the plugin with the given name, or empty if not found
136+
* @throws IllegalArgumentException if name is null
137+
*/
138+
public static Optional<StoragePlugin> findPlugin(String name) {
139+
if (name == null) {
140+
throw new IllegalArgumentException("Plugin name must not be null");
141+
}
142+
143+
// Ensure plugins are loaded
144+
getAvailablePlugins();
145+
146+
return Optional.ofNullable(pluginsByName.get(name));
147+
}
148+
149+
150+
/**
151+
* Returns the names of all available plugins.
152+
*
153+
* @return unmodifiable set of plugin names (never null, may be empty)
154+
*/
155+
public static Set<String> getPluginNames() {
156+
return getAvailablePlugins().stream()
157+
.map(StoragePlugin::getName)
158+
.collect(Collectors.collectingAndThen(
159+
Collectors.toSet(),
160+
Collections::unmodifiableSet
161+
));
162+
}
163+
164+
/**
165+
* Reloads all plugins from the classpath.
166+
*/
167+
public static void reload() {
168+
synchronized (StoragePluginManager.class) {
169+
cachedPlugins = null;
170+
pluginsByName.clear();
171+
}
172+
}
173+
174+
175+
}

src/main/java/fr/inria/corese/core/next/storagemanager/api/support/config/StorageConfig.java

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ public final class StorageConfig {
1919
*/
2020
private final boolean transactionSupport;
2121

22-
2322
/**
2423
* Private constructor — use {@link #builder()} to create instances.
2524
*
@@ -63,6 +62,26 @@ public Map<String, Object> getProperties() {
6362
return properties;
6463
}
6564

65+
/**
66+
* Returns whether transaction support is enabled.
67+
*
68+
* @return {@code true} if transaction support is enabled
69+
*/
70+
public boolean isTransactionSupport() {
71+
return transactionSupport;
72+
}
73+
74+
/**
75+
* Returns the storage type from properties.
76+
*
77+
* <p>This is a convenience method for {@code getProperty("type", String.class)}.
78+
* The type is used by the plugin system to select the appropriate StorageManager.
79+
*
80+
* @return the storage type, or empty if not set
81+
*/
82+
public Optional<String> getType() {
83+
return getProperty("type", String.class);
84+
}
6685

6786
/**
6887
* Creates a new {@link Builder} for constructing {@code StorageConfig} instances.
@@ -76,7 +95,8 @@ public static Builder builder() {
7695
@Override
7796
public String toString() {
7897
return "StorageConfig{" +
79-
"transactionSupport=" + transactionSupport +
98+
"type=" + getType().orElse("not set") +
99+
", transactionSupport=" + transactionSupport +
80100
", properties=" + properties +
81101
'}';
82102
}
@@ -86,10 +106,8 @@ public String toString() {
86106
*/
87107
public static final class Builder {
88108

89-
90109
private final Map<String, Object> properties = new HashMap<>();
91-
92-
private final boolean transactionSupport = false;
110+
private boolean transactionSupport = false; // NOT final!
93111

94112
/**
95113
* Private constructor — use {@link StorageConfig#builder()}.
@@ -117,6 +135,16 @@ public Builder property(String key, Object value) {
117135
return this;
118136
}
119137

138+
/**
139+
* Enables or disables transaction support.
140+
*
141+
* @param enable {@code true} to enable transaction support
142+
* @return this builder for method chaining
143+
*/
144+
public Builder transactionSupport(boolean enable) {
145+
this.transactionSupport = enable;
146+
return this;
147+
}
120148

121149
/**
122150
* Builds the {@link StorageConfig} instance with the current configuration.

src/main/java/fr/inria/corese/core/next/storagemanager/api/support/exception/ErrorCode.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ public enum ErrorCode {
3636

3737
/** Restart failed and rollback also failed (critical) */
3838
RESTART_FAILED_ROLLBACK_FAILED("RESTART_FAIL_ROLLBACK_FAIL",
39-
"Restart failed and unable to restore previous configuration"),;
39+
"Restart failed and unable to restore previous configuration"),
40+
41+
/**
42+
* Plugin failed to create StorageManager instance
43+
*/
44+
PLUGIN_CREATION_FAILED("PLUGIN_CREATION_FAILED", "Plugin failed to create StorageManager instance"),
45+
;
4046

4147
private final String code;
4248
private final String description;

0 commit comments

Comments
 (0)