From 8b20eae5c2c52ab2e6f054db9716bf93e8212ea0 Mon Sep 17 00:00:00 2001 From: "soumik.dutta" Date: Fri, 21 Jun 2024 14:54:43 +0530 Subject: [PATCH] Add LambdaMetafactory support to call simple getters efficiently --- .../execution/FixedLambdaInvoker.java | 22 +++++ .../metadata/execution/LambdaInvoker.java | 94 +++++++++++++++++++ .../query/DefaultMethodInvokerFactory.java | 23 ++++- 3 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 src/main/java/io/leangen/graphql/metadata/execution/FixedLambdaInvoker.java create mode 100644 src/main/java/io/leangen/graphql/metadata/execution/LambdaInvoker.java diff --git a/src/main/java/io/leangen/graphql/metadata/execution/FixedLambdaInvoker.java b/src/main/java/io/leangen/graphql/metadata/execution/FixedLambdaInvoker.java new file mode 100644 index 0000000..2349531 --- /dev/null +++ b/src/main/java/io/leangen/graphql/metadata/execution/FixedLambdaInvoker.java @@ -0,0 +1,22 @@ +package io.leangen.graphql.metadata.execution; + +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Method; +import java.util.function.Supplier; + +/** + * Created by soumik.dutta on 9/21/24. + */ +public class FixedLambdaInvoker extends LambdaInvoker { + final private Supplier targetSupplier; + + public FixedLambdaInvoker(final Supplier targetSupplier, final Method resolverMethod, final AnnotatedType enclosingType, final boolean useSetAccessible) throws Exception { + super(resolverMethod, enclosingType, useSetAccessible); + this.targetSupplier = targetSupplier; + } + + @Override + public Object execute(final Object target, final Object[] arguments) { + return this.lambdaGetter.apply(this.targetSupplier.get()); + } +} \ No newline at end of file diff --git a/src/main/java/io/leangen/graphql/metadata/execution/LambdaInvoker.java b/src/main/java/io/leangen/graphql/metadata/execution/LambdaInvoker.java new file mode 100644 index 0000000..13c7c5e --- /dev/null +++ b/src/main/java/io/leangen/graphql/metadata/execution/LambdaInvoker.java @@ -0,0 +1,94 @@ +package io.leangen.graphql.metadata.execution; + +import io.leangen.graphql.util.ClassUtils; + +import java.lang.invoke.*; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.Optional; +import java.util.function.Function; + +/** + * Created by soumik.dutta on 9/21/24. + */ +public class LambdaInvoker extends Executable { + private static final Parameter[] NO_PARAMETERS = {}; + private static final AnnotatedType[] NO_ANNOTATED_TYPES = {}; + final Function lambdaGetter; + private final AnnotatedType returnType; + + + public LambdaInvoker(final Method resolverMethod, final AnnotatedType enclosingType, final boolean useSetAccessible) throws Exception { + this.delegate = resolverMethod; + this.returnType = resolveReturnType(enclosingType); + final Optional> getter = createGetter(resolverMethod, useSetAccessible); + if (getter.isEmpty()) { + throw new Exception("Cannot create a lambda getter for " + resolverMethod.getName()); + } + + this.lambdaGetter = getter.get(); + } + + public static Optional> createGetter(final Method method, final boolean useSetAccessible) throws Exception { + if (method != null) { + if (method.getParameterCount() > 0) { + throw new Exception(method.getName() + " requires more than one argument"); + } + + try { + method.setAccessible(useSetAccessible); + MethodHandles.Lookup lookupMe = MethodHandles.lookup(); + MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(method.getDeclaringClass(), lookupMe); + MethodHandle virtualMethodHandle = lookup.unreflect(method); + + CallSite site = LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Function.class), + MethodType.methodType(Object.class, Object.class), virtualMethodHandle, + MethodType.methodType(method.getReturnType(), method.getDeclaringClass())); + + @SuppressWarnings("unchecked") Function getterFunction = (Function) site.getTarget() + .invokeExact(); + return Optional.of(getterFunction); + } catch (Throwable e) { + // + // if we cant make a dynamic lambda here, then we give up and let the old property fetching code do its thing + // this can happen on runtimes such as GraalVM native where LambdaMetafactory is not supported + // and will throw something like : + // + // com.oracle.svm.core.jdk.UnsupportedFeatureError: Defining hidden classes at runtime is not supported. + // at org.graalvm.nativeimage.builder/com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:89) + } + } + return Optional.empty(); + } + + @Override + public Object execute(final Object target, final Object[] args) { + return lambdaGetter.apply(target); + } + + @Override + final public AnnotatedType getReturnType() { + return returnType; + } + + private AnnotatedType resolveReturnType(final AnnotatedType enclosingType) { + return ClassUtils.getReturnType(delegate, enclosingType); + } + + + @Override + final public int getParameterCount() { + return 0; + } + + @Override + final public AnnotatedType[] getAnnotatedParameterTypes() { + return NO_ANNOTATED_TYPES; + } + + @Override + final public Parameter[] getParameters() { + return NO_PARAMETERS; + } +} \ No newline at end of file diff --git a/src/main/java/io/leangen/graphql/metadata/strategy/query/DefaultMethodInvokerFactory.java b/src/main/java/io/leangen/graphql/metadata/strategy/query/DefaultMethodInvokerFactory.java index 2b60f11..0aaf233 100644 --- a/src/main/java/io/leangen/graphql/metadata/strategy/query/DefaultMethodInvokerFactory.java +++ b/src/main/java/io/leangen/graphql/metadata/strategy/query/DefaultMethodInvokerFactory.java @@ -1,17 +1,32 @@ package io.leangen.graphql.metadata.strategy.query; -import io.leangen.graphql.metadata.execution.Executable; -import io.leangen.graphql.metadata.execution.FixedMethodInvoker; -import io.leangen.graphql.metadata.execution.MethodInvoker; +import io.leangen.graphql.metadata.execution.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.lang.reflect.AnnotatedType; import java.lang.reflect.Method; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; public class DefaultMethodInvokerFactory implements MethodInvokerFactory { + private static final Logger log = LoggerFactory.getLogger(DefaultMethodInvokerFactory.class); + private final AtomicBoolean USE_SET_ACCESSIBLE = new AtomicBoolean(true); + private final AtomicBoolean USE_LAMBDA_FACTORY = new AtomicBoolean(true); @Override public Executable create(Supplier targetSupplier, Method resolverMethod, AnnotatedType enclosingType, Class exposedType) { - return targetSupplier == null ? new MethodInvoker(resolverMethod, enclosingType) : new FixedMethodInvoker(targetSupplier, resolverMethod, enclosingType); + try { + if (USE_LAMBDA_FACTORY.get()) { + return targetSupplier == null ? new LambdaInvoker(resolverMethod, enclosingType, + USE_SET_ACCESSIBLE.get()) : new FixedLambdaInvoker(targetSupplier, resolverMethod, enclosingType, + USE_SET_ACCESSIBLE.get()); + } + } catch (Exception e) { + log.warn("Lambda Invokers could not be used for {} because {}", resolverMethod, e.toString()); + } + + return targetSupplier == null ? new MethodInvoker(resolverMethod, enclosingType) : new FixedMethodInvoker(targetSupplier, + resolverMethod, enclosingType); } }