From 8d95c668e2ecc898b374220f7001f7b8ac70be06 Mon Sep 17 00:00:00 2001 From: JamesChenX Date: Sat, 27 Apr 2024 20:06:47 +0800 Subject: [PATCH] Add default implementation of proxied Object methods for JavaScript extensions --- .../JsExtensionPointInvocationHandler.java | 58 ++++++++++--------- .../common/infra/plugin/JsPluginFactory.java | 6 +- .../infra/plugin/JsTurmsExtensionAdaptor.java | 8 ++- .../infra/plugin/script/ValueInspector.java | 3 + .../infra/plugin/JsPluginManagerTests.java | 21 +++++++ 5 files changed, 67 insertions(+), 29 deletions(-) diff --git a/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/JsExtensionPointInvocationHandler.java b/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/JsExtensionPointInvocationHandler.java index a4200cec42..6c0f583f84 100644 --- a/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/JsExtensionPointInvocationHandler.java +++ b/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/JsExtensionPointInvocationHandler.java @@ -20,7 +20,6 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map; -import java.util.Set; import jakarta.annotation.Nullable; import org.graalvm.polyglot.Value; @@ -35,45 +34,52 @@ */ public class JsExtensionPointInvocationHandler implements InvocationHandler { - private static final Set OBJECT_METHODS = Set.of("equals", "hashCode", "toString"); - + private final String className; private final Map, Map> extensionPointToFunction; public JsExtensionPointInvocationHandler( + String className, Map, Map> extensionPointToFunction) { + this.className = className; this.extensionPointToFunction = extensionPointToFunction; } @Nullable @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - if (OBJECT_METHODS.contains(method.getName())) { - return method.invoke(proxy, args); - } Map nameToFunction = extensionPointToFunction.get(method.getDeclaringClass()); + Value function = null; Class returnType = method.getReturnType(); - // We only check Mono because we never use Flux - // for the interfaces of extension points - boolean isAsync = returnType.isAssignableFrom(Mono.class); - if (nameToFunction == null) { - return isAsync - ? Mono.empty() - : null; + String methodName = method.getName(); + if (nameToFunction != null) { + function = nameToFunction.get(methodName); } - Value function = nameToFunction.get(method.getName()); + // We only check if it isMono because we + // never use Flux for the interfaces of extension points. + boolean isAsync = returnType.isAssignableFrom(Mono.class); if (function == null) { - if (isAsync) { - // Keep it simple because we only use - // the return type of Mono currently. - return Mono.empty(); - } else if (void.class == returnType) { - return null; - } else { - String message = "Could not find a default return value for the return type: " - + returnType.getName(); - throw new ScriptExecutionException(message, ScriptExceptionSource.HOST); - } + return switch (methodName) { + case "equals" -> args.length >= 1 && proxy == args[0]; + case "hashCode" -> System.identityHashCode(proxy); + case "toString" -> className + + "@" + + Integer.toHexString(System.identityHashCode(proxy)); + default -> { + if (isAsync) { + // Keep it simple because we only use + // the return type of Mono currently. + yield Mono.empty(); + } else if (void.class == returnType) { + yield null; + } else { + String message = + "Could not find a default return value for the return type: " + + returnType.getName(); + throw new ScriptExecutionException(message, ScriptExceptionSource.HOST); + } + } + }; } Value returnValue; try { @@ -82,7 +88,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl : function.execute(args); } catch (Exception e) { String message = "Failed to execute the function \"" - + method.getName() + + methodName + "\" in the class: " + method.getDeclaringClass() .getName(); diff --git a/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/JsPluginFactory.java b/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/JsPluginFactory.java index d35ce65434..2c46003f13 100644 --- a/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/JsPluginFactory.java +++ b/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/JsPluginFactory.java @@ -224,7 +224,9 @@ private static JsTurmsExtensionAdaptor createExtension( ExtensionPoint extensionPointProxy = (ExtensionPoint) Proxy.newProxyInstance(JsPluginFactory.class.getClassLoader(), info.extensionPointClasses.toArray(new Class[0]), - new JsExtensionPointInvocationHandler(info.functions)); + new JsExtensionPointInvocationHandler( + extensionClass.getMetaQualifiedName(), + info.functions)); JsTurmsExtensionAdaptor adaptor = new JsTurmsExtensionAdaptor( extensionPointProxy, info.extensionPointClasses, @@ -292,7 +294,7 @@ private static Class parseExtensionPointClass(Value cl } } - private static record ExtensionClassInfo( + private record ExtensionClassInfo( List> extensionPointClasses, Map, Map> functions ) { diff --git a/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/JsTurmsExtensionAdaptor.java b/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/JsTurmsExtensionAdaptor.java index 453ab30fbf..e6c5377ce8 100644 --- a/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/JsTurmsExtensionAdaptor.java +++ b/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/JsTurmsExtensionAdaptor.java @@ -18,6 +18,7 @@ package im.turms.server.common.infra.plugin; import java.util.List; +import jakarta.annotation.Nullable; import lombok.Getter; import org.graalvm.polyglot.Value; @@ -35,9 +36,14 @@ public class JsTurmsExtensionAdaptor extends TurmsExtension { private final ExtensionPoint proxy; @Getter private final List> extensionPointClasses; + + @Nullable private final Value onStarted; + @Nullable private final Value onStopped; + @Nullable private final Value onResumed; + @Nullable private final Value onPaused; public JsTurmsExtensionAdaptor( @@ -77,7 +83,7 @@ protected Mono pause() { return execute(onPaused); } - private Mono execute(Value callback) { + private Mono execute(@Nullable Value callback) { if (callback == null) { return Mono.empty(); } diff --git a/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/script/ValueInspector.java b/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/script/ValueInspector.java index 609dd8bc43..04aa78e813 100644 --- a/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/script/ValueInspector.java +++ b/turms-server-common/src/main/java/im/turms/server/common/infra/plugin/script/ValueInspector.java @@ -17,6 +17,8 @@ package im.turms.server.common.infra.plugin.script; +import jakarta.annotation.Nullable; + import org.graalvm.polyglot.Value; /** @@ -35,6 +37,7 @@ public static boolean isFalse(Value bool) { return !isTrue(bool); } + @Nullable public static Value returnIfFunction(Value value) { return value != null && value.canExecute() ? value diff --git a/turms-server-common/src/test/java/unit/im/turms/server/common/infra/plugin/JsPluginManagerTests.java b/turms-server-common/src/test/java/unit/im/turms/server/common/infra/plugin/JsPluginManagerTests.java index 6c5e42e21c..2c36a3f161 100644 --- a/turms-server-common/src/test/java/unit/im/turms/server/common/infra/plugin/JsPluginManagerTests.java +++ b/turms-server-common/src/test/java/unit/im/turms/server/common/infra/plugin/JsPluginManagerTests.java @@ -55,6 +55,27 @@ */ class JsPluginManagerTests { + @Test + void testEquals() { + MyExtensionPointForJs extensionPoint = createExtensionPoint(); + assertThat(extensionPoint.equals(null)).isFalse(); + assertThat(extensionPoint.equals(new Object())).isFalse(); + assertThat(extensionPoint.equals(this)).isFalse(); + assertThat(extensionPoint.equals(extensionPoint)).isTrue(); + } + + @Test + void testHashCode() { + MyExtensionPointForJs extensionPoint = createExtensionPoint(); + assertThat(extensionPoint.hashCode()).isPositive(); + } + + @Test + void testToString() { + MyExtensionPointForJs extensionPoint = createExtensionPoint(); + assertThat(extensionPoint.toString()).isNotEmpty(); + } + @Test void testBool_forPlainValue() { MyExtensionPointForJs extensionPoint = createExtensionPoint();