Skip to content

Commit

Permalink
Add default implementation of proxied Object methods for JavaScript e…
Browse files Browse the repository at this point in the history
…xtensions
  • Loading branch information
JamesChenX committed Apr 27, 2024
1 parent 8d675df commit 8d95c66
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 29 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,45 +34,52 @@
*/
public class JsExtensionPointInvocationHandler implements InvocationHandler {

private static final Set<String> OBJECT_METHODS = Set.of("equals", "hashCode", "toString");

private final String className;
private final Map<Class<? extends ExtensionPoint>, Map<String, Value>> extensionPointToFunction;

public JsExtensionPointInvocationHandler(
String className,
Map<Class<? extends ExtensionPoint>, Map<String, Value>> 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<String, Value> 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 {
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -292,7 +294,7 @@ private static Class<? extends ExtensionPoint> parseExtensionPointClass(Value cl
}
}

private static record ExtensionClassInfo(
private record ExtensionClassInfo(
List<Class<? extends ExtensionPoint>> extensionPointClasses,
Map<Class<? extends ExtensionPoint>, Map<String, Value>> functions
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -35,9 +36,14 @@ public class JsTurmsExtensionAdaptor extends TurmsExtension {
private final ExtensionPoint proxy;
@Getter
private final List<Class<? extends ExtensionPoint>> extensionPointClasses;

@Nullable
private final Value onStarted;
@Nullable
private final Value onStopped;
@Nullable
private final Value onResumed;
@Nullable
private final Value onPaused;

public JsTurmsExtensionAdaptor(
Expand Down Expand Up @@ -77,7 +83,7 @@ protected Mono<Void> pause() {
return execute(onPaused);
}

private Mono<Void> execute(Value callback) {
private Mono<Void> execute(@Nullable Value callback) {
if (callback == null) {
return Mono.empty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

package im.turms.server.common.infra.plugin.script;

import jakarta.annotation.Nullable;

import org.graalvm.polyglot.Value;

/**
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 8d95c66

Please sign in to comment.