package com.xfestudio.xfeservermanager.core.trigger;
import com.xfestudio.xfeservermanager.api.trigger.TriggerExtension;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
/** Discovers declarative trigger extensions while isolating invalid providers. */
public final class TriggerExtensionRegistry {
private TriggerExtensionRegistry() { }
public static Snapshot discover(Collection<TriggerExtension.Descriptor> builtIns, ClassLoader classLoader) {
Objects.requireNonNull(builtIns, "builtIns");
Map<String, TriggerExtension.Descriptor> descriptors = new LinkedHashMap<>();
builtIns.forEach(descriptor -> descriptors.put(key(descriptor), descriptor));
Map<String, TriggerExtension.FunctionHandler> functions = new LinkedHashMap<>();
Map<String, TriggerExtension.ActionHandler> actions = new LinkedHashMap<>();
List<String> diagnostics = new ArrayList<>();
List<ServiceLoader.Provider<TriggerExtension>> providers;
try {
providers = ServiceLoader.load(TriggerExtension.class,
classLoader == null ? TriggerExtensionRegistry.class.getClassLoader() : classLoader)
.stream().toList();
} catch (RuntimeException | java.util.ServiceConfigurationError failure) {
diagnostics.add("trigger extension discovery failed: " + safeMessage(failure));
return snapshot(descriptors, functions, actions, diagnostics);
}
for (ServiceLoader.Provider<TriggerExtension> provider : providers) {
try {
Map<String, TriggerExtension.Descriptor> candidateDescriptors =
new LinkedHashMap<>(descriptors);
Map<String, TriggerExtension.FunctionHandler> candidateFunctions =
new LinkedHashMap<>(functions);
Map<String, TriggerExtension.ActionHandler> candidateActions =
new LinkedHashMap<>(actions);
register(provider.get(), candidateDescriptors, candidateFunctions, candidateActions);
descriptors.clear();
descriptors.putAll(candidateDescriptors);
functions.clear();
functions.putAll(candidateFunctions);
actions.clear();
actions.putAll(candidateActions);
} catch (RuntimeException | java.util.ServiceConfigurationError failure) {
diagnostics.add(provider.type().getName() + ": " + safeMessage(failure));
}
}
return snapshot(descriptors, functions, actions, diagnostics);
}
static void register(
TriggerExtension extension,
Map<String, TriggerExtension.Descriptor> descriptors) {
register(extension, descriptors, new LinkedHashMap<>(), new LinkedHashMap<>());
}
static void register(
TriggerExtension extension,
Map<String, TriggerExtension.Descriptor> descriptors,
Map<String, TriggerExtension.FunctionHandler> functions,
Map<String, TriggerExtension.ActionHandler> actions) {
Objects.requireNonNull(extension, "extension");
String namespace = identifier(extension.namespace(), "extension namespace");
String version = Objects.requireNonNull(extension.version(), "extension version").strip();
if (!version.matches("0|[1-9]\\d*(?:\\.(?:0|[1-9]\\d*)){2}(?:-[0-9A-Za-z.-]+)?")) {
throw new IllegalArgumentException("extension version must be semantic: " + version);
}
Collection<TriggerExtension.Descriptor> contributions = Objects.requireNonNull(
extension.descriptors(), "extension descriptors");
if (contributions.size() > 1_024) throw new IllegalArgumentException("extension has too many descriptors");
for (TriggerExtension.Descriptor contribution : contributions) {
Objects.requireNonNull(contribution, "extension descriptor");
validateTransportValue(contribution.metadata(), 0);
TriggerExtension.Descriptor existing = descriptors.get(key(contribution));
if (existing != null) {
if (existing.kind() != contribution.kind()) {
throw new IllegalArgumentException("extension cannot change descriptor kind: "
+ contribution.id());
}
TriggerExtension.Risk raised = TriggerExtension.Risk.atLeast(
existing.risk(), contribution.risk());
if (raised != existing.risk()) {
Map<String, Object> metadata = new LinkedHashMap<>(existing.metadata());
metadata.put("riskRaisedBy", namespace + '@' + version);
descriptors.put(key(existing), new TriggerExtension.Descriptor(
existing.id(), existing.kind(), existing.displayName(), existing.description(),
existing.parameters(), existing.returnType(), existing.purity(),
existing.threadAffinity(), raised, existing.supportedPlatforms(),
existing.applicableEvents(), metadata));
}
continue;
}
if (!contribution.id().startsWith(namespace + ".")
&& !contribution.id().startsWith(namespace + ":")) {
throw new IllegalArgumentException("extension descriptor must use namespace " + namespace);
}
descriptors.put(key(contribution), contribution);
}
Map<String, TriggerExtension.FunctionHandler> contributedFunctions = Map.copyOf(
Objects.requireNonNull(extension.functionHandlers(), "extension function handlers"));
Map<String, TriggerExtension.ActionHandler> contributedActions = Map.copyOf(
Objects.requireNonNull(extension.actionHandlers(), "extension action handlers"));
contributedFunctions.forEach((id, handler) -> {
String normalized = capabilityIdentifier(id, namespace);
TriggerExtension.Descriptor descriptor = descriptors.get("VALUE_FUNCTION:" + normalized);
if (descriptor == null) descriptor = descriptors.get("CONDITION_FUNCTION:" + normalized);
if (descriptor == null) {
throw new IllegalArgumentException("extension function handler has no descriptor: " + normalized);
}
if (descriptor.threadAffinity() != TriggerExtension.ThreadAffinity.BACKGROUND
|| descriptor.purity() == TriggerExtension.Purity.SIDE_EFFECT) {
throw new IllegalArgumentException("extension function handler must be background and side-effect free");
}
if (functions.putIfAbsent(normalized, Objects.requireNonNull(handler, "function handler")) != null) {
throw new IllegalArgumentException("duplicate extension function handler: " + normalized);
}
});
contributedActions.forEach((id, handler) -> {
String normalized = capabilityIdentifier(id, namespace);
TriggerExtension.Descriptor descriptor = descriptors.get("ACTION:" + normalized);
if (descriptor == null) {
throw new IllegalArgumentException("extension action handler has no descriptor: " + normalized);
}
if (descriptor.threadAffinity() != TriggerExtension.ThreadAffinity.SERVER
|| descriptor.purity() != TriggerExtension.Purity.SIDE_EFFECT) {
throw new IllegalArgumentException("extension action handler must be a server-thread side effect");
}
if (actions.putIfAbsent(normalized, Objects.requireNonNull(handler, "action handler")) != null) {
throw new IllegalArgumentException("duplicate extension action handler: " + normalized);
}
});
}
private static Snapshot snapshot(
Map<String, TriggerExtension.Descriptor> descriptors,
Map<String, TriggerExtension.FunctionHandler> functions,
Map<String, TriggerExtension.ActionHandler> actions,
List<String> diagnostics) {
List<TriggerExtension.Descriptor> ordered = descriptors.values().stream()
.sorted(Comparator.comparing((TriggerExtension.Descriptor value) -> value.kind().name())
.thenComparing(TriggerExtension.Descriptor::id)).toList();
long revision = 0xcbf29ce484222325L;
for (TriggerExtension.Descriptor descriptor : ordered) {
String signature = descriptor.id() + '|' + descriptor.kind() + '|' + descriptor.returnType()
+ '|' + descriptor.purity() + '|' + descriptor.threadAffinity() + '|' + descriptor.risk()
+ '|' + descriptor.supportedPlatforms().stream().sorted().toList()
+ '|' + descriptor.applicableEvents().stream().sorted().toList()
+ '|' + descriptor.parameters();
for (int index = 0; index < signature.length(); index++) {
revision ^= signature.charAt(index);
revision *= 0x100000001b3L;
}
}
return new Snapshot(ordered, List.copyOf(diagnostics), Map.copyOf(functions), Map.copyOf(actions),
Math.max(1L, revision & Long.MAX_VALUE));
}
static void validateTransportValue(Object value, int depth) {
if (depth > 6) throw new IllegalArgumentException("extension metadata is nested too deeply");
if (value == null || value instanceof String || value instanceof Boolean) return;
if (value instanceof Number number) {
if (!Double.isFinite(number.doubleValue())) {
throw new IllegalArgumentException("extension metadata contains a non-finite number");
}
return;
}
if (value instanceof Map<?, ?> map) {
if (map.size() > 256) throw new IllegalArgumentException("extension metadata map is too large");
map.forEach((key, entry) -> {
if (!(key instanceof String)) {
throw new IllegalArgumentException("extension metadata keys must be strings");
}
validateTransportValue(entry, depth + 1);
});
return;
}
if (value instanceof Collection<?> values) {
if (values.size() > 256) throw new IllegalArgumentException("extension metadata list is too large");
values.forEach(entry -> validateTransportValue(entry, depth + 1));
return;
}
throw new IllegalArgumentException("extension metadata is not transport-safe: "
+ value.getClass().getSimpleName());
}
private static String identifier(String value, String field) {
String normalized = Objects.requireNonNull(value, field).strip().toLowerCase(Locale.ROOT);
if (!normalized.matches("[a-z][a-z0-9_-]{0,63}")) {
throw new IllegalArgumentException(field + " contains unsupported characters");
}
return normalized;
}
private static String capabilityIdentifier(String value, String namespace) {
String normalized = Objects.requireNonNull(value, "handler id").strip().toLowerCase(Locale.ROOT);
if (!normalized.matches("[a-z][a-z0-9_.:_-]{0,127}")) {
throw new IllegalArgumentException("handler id contains unsupported characters");
}
if (!normalized.startsWith(namespace + ".") && !normalized.startsWith(namespace + ":")) {
throw new IllegalArgumentException("extension handler must use namespace " + namespace);
}
return normalized;
}
private static String safeMessage(Throwable failure) {
return failure.getMessage() == null ? failure.getClass().getSimpleName() : failure.getMessage();
}
private static String key(TriggerExtension.Descriptor descriptor) {
return descriptor.kind() + ":" + descriptor.id();
}
public record Snapshot(
List<TriggerExtension.Descriptor> descriptors,
List<String> diagnostics,
Map<String, TriggerExtension.FunctionHandler> functionHandlers,
Map<String, TriggerExtension.ActionHandler> actionHandlers,
long catalogRevision) {
public Snapshot {
descriptors = List.copyOf(descriptors);
diagnostics = List.copyOf(diagnostics);
functionHandlers = Map.copyOf(functionHandlers);
actionHandlers = Map.copyOf(actionHandlers);
}
}
}
package com.xfestudio.xfeservermanager.core.trigger;
import com.xfestudio.xfeservermanager.api.trigger.TriggerExtension;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;
/** Discovers declarative trigger extensions while isolating invalid providers. */
public final class TriggerExtensionRegistry {
private TriggerExtensionRegistry() { }
public static Snapshot discover(Collection<TriggerExtension.Descriptor> builtIns, ClassLoader classLoader) {
Objects.requireNonNull(builtIns, "builtIns");
Map<String, TriggerExtension.Descriptor> descriptors = new LinkedHashMap<>();
builtIns.forEach(descriptor -> descriptors.put(key(descriptor), descriptor));
Map<String, TriggerExtension.FunctionHandler> functions = new LinkedHashMap<>();
Map<String, TriggerExtension.ActionHandler> actions = new LinkedHashMap<>();
List<String> diagnostics = new ArrayList<>();
List<ServiceLoader.Provider<TriggerExtension>> providers;
try {
providers = ServiceLoader.load(TriggerExtension.class,
classLoader == null ? TriggerExtensionRegistry.class.getClassLoader() : classLoader)
.stream().toList();
} catch (RuntimeException | java.util.ServiceConfigurationError failure) {
diagnostics.add("trigger extension discovery failed: " + safeMessage(failure));
return snapshot(descriptors, functions, actions, diagnostics);
}
for (ServiceLoader.Provider<TriggerExtension> provider : providers) {
try {
Map<String, TriggerExtension.Descriptor> candidateDescriptors =
new LinkedHashMap<>(descriptors);
Map<String, TriggerExtension.FunctionHandler> candidateFunctions =
new LinkedHashMap<>(functions);
Map<String, TriggerExtension.ActionHandler> candidateActions =
new LinkedHashMap<>(actions);
register(provider.get(), candidateDescriptors, candidateFunctions, candidateActions);
descriptors.clear();
descriptors.putAll(candidateDescriptors);
functions.clear();
functions.putAll(candidateFunctions);
actions.clear();
actions.putAll(candidateActions);
} catch (RuntimeException | java.util.ServiceConfigurationError failure) {
diagnostics.add(provider.type().getName() + ": " + safeMessage(failure));
}
}
return snapshot(descriptors, functions, actions, diagnostics);
}
static void register(
TriggerExtension extension,
Map<String, TriggerExtension.Descriptor> descriptors) {
register(extension, descriptors, new LinkedHashMap<>(), new LinkedHashMap<>());
}
static void register(
TriggerExtension extension,
Map<String, TriggerExtension.Descriptor> descriptors,
Map<String, TriggerExtension.FunctionHandler> functions,
Map<String, TriggerExtension.ActionHandler> actions) {
Objects.requireNonNull(extension, "extension");
String namespace = identifier(extension.namespace(), "extension namespace");
String version = Objects.requireNonNull(extension.version(), "extension version").strip();
if (!version.matches("0|[1-9]\\d*(?:\\.(?:0|[1-9]\\d*)){2}(?:-[0-9A-Za-z.-]+)?")) {
throw new IllegalArgumentException("extension version must be semantic: " + version);
}
Collection<TriggerExtension.Descriptor> contributions = Objects.requireNonNull(
extension.descriptors(), "extension descriptors");
if (contributions.size() > 1_024) throw new IllegalArgumentException("extension has too many descriptors");
for (TriggerExtension.Descriptor contribution : contributions) {
Objects.requireNonNull(contribution, "extension descriptor");
validateTransportValue(contribution.metadata(), 0);
TriggerExtension.Descriptor existing = descriptors.get(key(contribution));
if (existing != null) {
if (existing.kind() != contribution.kind()) {
throw new IllegalArgumentException("extension cannot change descriptor kind: "
+ contribution.id());
}
TriggerExtension.Risk raised = TriggerExtension.Risk.atLeast(
existing.risk(), contribution.risk());
if (raised != existing.risk()) {
Map<String, Object> metadata = new LinkedHashMap<>(existing.metadata());
metadata.put("riskRaisedBy", namespace + '@' + version);
descriptors.put(key(existing), new TriggerExtension.Descriptor(
existing.id(), existing.kind(), existing.displayName(), existing.description(),
existing.parameters(), existing.returnType(), existing.purity(),
existing.threadAffinity(), raised, existing.supportedPlatforms(),
existing.applicableEvents(), metadata));
}
continue;
}
if (!contribution.id().startsWith(namespace + ".")
&& !contribution.id().startsWith(namespace + ":")) {
throw new IllegalArgumentException("extension descriptor must use namespace " + namespace);
}
descriptors.put(key(contribution), contribution);
}
Map<String, TriggerExtension.FunctionHandler> contributedFunctions = Map.copyOf(
Objects.requireNonNull(extension.functionHandlers(), "extension function handlers"));
Map<String, TriggerExtension.ActionHandler> contributedActions = Map.copyOf(
Objects.requireNonNull(extension.actionHandlers(), "extension action handlers"));
contributedFunctions.forEach((id, handler) -> {
String normalized = capabilityIdentifier(id, namespace);
TriggerExtension.Descriptor descriptor = descriptors.get("VALUE_FUNCTION:" + normalized);
if (descriptor == null) descriptor = descriptors.get("CONDITION_FUNCTION:" + normalized);
if (descriptor == null) {
throw new IllegalArgumentException("extension function handler has no descriptor: " + normalized);
}
if (descriptor.threadAffinity() != TriggerExtension.ThreadAffinity.BACKGROUND
|| descriptor.purity() == TriggerExtension.Purity.SIDE_EFFECT) {
throw new IllegalArgumentException("extension function handler must be background and side-effect free");
}
if (functions.putIfAbsent(normalized, Objects.requireNonNull(handler, "function handler")) != null) {
throw new IllegalArgumentException("duplicate extension function handler: " + normalized);
}
});
contributedActions.forEach((id, handler) -> {
String normalized = capabilityIdentifier(id, namespace);
TriggerExtension.Descriptor descriptor = descriptors.get("ACTION:" + normalized);
if (descriptor == null) {
throw new IllegalArgumentException("extension action handler has no descriptor: " + normalized);
}
if (descriptor.threadAffinity() != TriggerExtension.ThreadAffinity.SERVER
|| descriptor.purity() != TriggerExtension.Purity.SIDE_EFFECT) {
throw new IllegalArgumentException("extension action handler must be a server-thread side effect");
}
if (actions.putIfAbsent(normalized, Objects.requireNonNull(handler, "action handler")) != null) {
throw new IllegalArgumentException("duplicate extension action handler: " + normalized);
}
});
}
private static Snapshot snapshot(
Map<String, TriggerExtension.Descriptor> descriptors,
Map<String, TriggerExtension.FunctionHandler> functions,
Map<String, TriggerExtension.ActionHandler> actions,
List<String> diagnostics) {
List<TriggerExtension.Descriptor> ordered = descriptors.values().stream()
.sorted(Comparator.comparing((TriggerExtension.Descriptor value) -> value.kind().name())
.thenComparing(TriggerExtension.Descriptor::id)).toList();
long revision = 0xcbf29ce484222325L;
for (TriggerExtension.Descriptor descriptor : ordered) {
String signature = descriptor.id() + '|' + descriptor.kind() + '|' + descriptor.returnType()
+ '|' + descriptor.purity() + '|' + descriptor.threadAffinity() + '|' + descriptor.risk()
+ '|' + descriptor.supportedPlatforms().stream().sorted().toList()
+ '|' + descriptor.applicableEvents().stream().sorted().toList()
+ '|' + descriptor.parameters();
for (int index = 0; index < signature.length(); index++) {
revision ^= signature.charAt(index);
revision *= 0x100000001b3L;
}
}
return new Snapshot(ordered, List.copyOf(diagnostics), Map.copyOf(functions), Map.copyOf(actions),
Math.max(1L, revision & Long.MAX_VALUE));
}
static void validateTransportValue(Object value, int depth) {
if (depth > 6) throw new IllegalArgumentException("extension metadata is nested too deeply");
if (value == null || value instanceof String || value instanceof Boolean) return;
if (value instanceof Number number) {
if (!Double.isFinite(number.doubleValue())) {
throw new IllegalArgumentException("extension metadata contains a non-finite number");
}
return;
}
if (value instanceof Map<?, ?> map) {
if (map.size() > 256) throw new IllegalArgumentException("extension metadata map is too large");
map.forEach((key, entry) -> {
if (!(key instanceof String)) {
throw new IllegalArgumentException("extension metadata keys must be strings");
}
validateTransportValue(entry, depth + 1);
});
return;
}
if (value instanceof Collection<?> values) {
if (values.size() > 256) throw new IllegalArgumentException("extension metadata list is too large");
values.forEach(entry -> validateTransportValue(entry, depth + 1));
return;
}
throw new IllegalArgumentException("extension metadata is not transport-safe: "
+ value.getClass().getSimpleName());
}
private static String identifier(String value, String field) {
String normalized = Objects.requireNonNull(value, field).strip().toLowerCase(Locale.ROOT);
if (!normalized.matches("[a-z][a-z0-9_-]{0,63}")) {
throw new IllegalArgumentException(field + " contains unsupported characters");
}
return normalized;
}
private static String capabilityIdentifier(String value, String namespace) {
String normalized = Objects.requireNonNull(value, "handler id").strip().toLowerCase(Locale.ROOT);
if (!normalized.matches("[a-z][a-z0-9_.:_-]{0,127}")) {
throw new IllegalArgumentException("handler id contains unsupported characters");
}
if (!normalized.startsWith(namespace + ".") && !normalized.startsWith(namespace + ":")) {
throw new IllegalArgumentException("extension handler must use namespace " + namespace);
}
return normalized;
}
private static String safeMessage(Throwable failure) {
return failure.getMessage() == null ? failure.getClass().getSimpleName() : failure.getMessage();
}
private static String key(TriggerExtension.Descriptor descriptor) {
return descriptor.kind() + ":" + descriptor.id();
}
public record Snapshot(
List<TriggerExtension.Descriptor> descriptors,
List<String> diagnostics,
Map<String, TriggerExtension.FunctionHandler> functionHandlers,
Map<String, TriggerExtension.ActionHandler> actionHandlers,
long catalogRevision) {
public Snapshot {
descriptors = List.copyOf(descriptors);
diagnostics = List.copyOf(diagnostics);
functionHandlers = Map.copyOf(functionHandlers);
actionHandlers = Map.copyOf(actionHandlers);
}
}
}