From 4723aa05ca27c26d75fe9cafee78421a38e6b14f Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 21 May 2020 10:52:43 +0200 Subject: [PATCH] First attempt --- .../BeanRegistrationPhaseBuildItem.java | 2 +- .../InterceptedStaticMethodBuildItem.java | 63 +++ .../InterceptedStaticMethodsProcessor.java | 533 ++++++++++++++++++ .../InterceptedStaticMethodTest.java | 79 +++ .../InterceptedStaticMethodsRecorder.java | 22 + .../TransactionalInterceptorBase.java | 6 +- .../processor/AnnotationLiteralProcessor.java | 9 +- .../quarkus/arc/processor/BeanDeployment.java | 8 +- .../quarkus/arc/processor/BeanProcessor.java | 10 +- .../arc/processor/InterceptorInfo.java | 2 +- .../arc/processor/MethodDescriptors.java | 124 ++-- .../arc/processor/SubclassGenerator.java | 13 +- .../io/quarkus/arc/impl/ArcContainerImpl.java | 6 + ...ta.java => InterceptedMethodMetadata.java} | 4 +- .../arc/impl/InterceptedStaticMethods.java | 41 ++ .../main/java/io/quarkus/it/panache/Beer.java | 5 + .../TransactionalPanacheEntityBaseTest.java | 29 + .../panache/TransactionalRepositoryTest.java | 3 + 18 files changed, 885 insertions(+), 74 deletions(-) create mode 100644 extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java create mode 100644 extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java create mode 100644 extensions/arc/deployment/src/test/java/io/quarkus/arc/test/interceptor/staticmethods/InterceptedStaticMethodTest.java create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java rename independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/{SubclassMethodMetadata.java => InterceptedMethodMetadata.java} (70%) create mode 100644 independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InterceptedStaticMethods.java create mode 100644 integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TransactionalPanacheEntityBaseTest.java diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanRegistrationPhaseBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanRegistrationPhaseBuildItem.java index fe67ace4ea810c..ba956c71f37b7a 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanRegistrationPhaseBuildItem.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/BeanRegistrationPhaseBuildItem.java @@ -36,7 +36,7 @@ public RegistrationContext getContext() { return context; } - BeanProcessor getBeanProcessor() { + public BeanProcessor getBeanProcessor() { return beanProcessor; } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java new file mode 100644 index 00000000000000..989467c8155590 --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodBuildItem.java @@ -0,0 +1,63 @@ +package io.quarkus.arc.deployment.staticmethods; + +import java.util.List; +import java.util.Set; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.MethodInfo; + +import io.quarkus.arc.processor.InterceptorInfo; +import io.quarkus.builder.item.MultiBuildItem; +import io.quarkus.deployment.util.HashUtil; + +/** + * Represents an intercepted static method. + */ +public final class InterceptedStaticMethodBuildItem extends MultiBuildItem { + + private final MethodInfo method; + private final List interceptors; + private final Set bindings; + private final String hash; + + InterceptedStaticMethodBuildItem(MethodInfo method, Set bindings, List interceptors) { + this.method = method; + this.interceptors = interceptors; + this.bindings = bindings; + this.hash = HashUtil.sha1(method.declaringClass().name().toString() + method.toString()); + } + + public ClassInfo getTarget() { + return method.declaringClass(); + } + + public MethodInfo getMethod() { + return method; + } + + /** + * + * @return the list of interceptors that should be applied + */ + public List getInterceptors() { + return interceptors; + } + + /** + * + * @return the set of interceptor bindings + */ + public Set getBindings() { + return bindings; + } + + /** + * + * @return a unique hash that could be used to indentify the method + */ + public String getHash() { + return hash; + } + +} diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java new file mode 100644 index 00000000000000..1788566eca1725 --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/staticmethods/InterceptedStaticMethodsProcessor.java @@ -0,0 +1,533 @@ +package io.quarkus.arc.deployment.staticmethods; + +import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT; +import static org.objectweb.asm.Opcodes.ACC_FINAL; +import static org.objectweb.asm.Opcodes.ACC_PRIVATE; +import static org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static org.objectweb.asm.Opcodes.ACC_STATIC; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.stream.Collectors; + +import javax.enterprise.context.spi.Contextual; +import javax.enterprise.inject.spi.InterceptionType; +import javax.interceptor.InvocationContext; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationTarget.Kind; +import org.jboss.jandex.ClassInfo; +import org.jboss.jandex.DotName; +import org.jboss.jandex.IndexView; +import org.jboss.jandex.MethodInfo; +import org.jboss.jandex.Type; +import org.jboss.logging.Logger; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import io.quarkus.arc.InjectableInterceptor; +import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem; +import io.quarkus.arc.deployment.BeanContainerBuildItem; +import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem; +import io.quarkus.arc.deployment.BeanRegistrationPhaseBuildItem.BeanConfiguratorBuildItem; +import io.quarkus.arc.impl.CreationalContextImpl; +import io.quarkus.arc.impl.InterceptedMethodMetadata; +import io.quarkus.arc.impl.InterceptedStaticMethods; +import io.quarkus.arc.impl.InterceptedStaticMethods.StaticMethod; +import io.quarkus.arc.processor.AnnotationLiteralProcessor; +import io.quarkus.arc.processor.BeanDeployment; +import io.quarkus.arc.processor.BeanProcessor; +import io.quarkus.arc.processor.DotNames; +import io.quarkus.arc.processor.InterceptorInfo; +import io.quarkus.arc.processor.InterceptorResolver; +import io.quarkus.arc.processor.MethodDescriptors; +import io.quarkus.arc.runtime.InterceptedStaticMethodsRecorder; +import io.quarkus.deployment.GeneratedClassGizmoAdaptor; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; +import io.quarkus.deployment.builditem.GeneratedClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveMethodBuildItem; +import io.quarkus.gizmo.BytecodeCreator; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.DescriptorUtils; +import io.quarkus.gizmo.FunctionCreator; +import io.quarkus.gizmo.Gizmo; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.gizmo.ResultHandle; + +public class InterceptedStaticMethodsProcessor { + + private static final Logger LOGGER = Logger.getLogger(InterceptedStaticMethodsProcessor.class); + + static final MethodDescriptor INTERCEPTED_STATIC_METHODS_REGISTER = MethodDescriptor + .ofMethod(InterceptedStaticMethods.class, "register", void.class, String.class, StaticMethod.class); + static final MethodDescriptor INTERCEPTED_STATIC_METHODS_AROUND_INVOKE = MethodDescriptor + .ofMethod(InterceptedStaticMethods.class, "aroundInvoke", Object.class, String.class, Object[].class); + + private static final String ORGINAL_METHOD_COPY_SUFFIX = "_original"; + private static final String INITIALIZER_CLASS_SUFFIX = "_InterceptorInitializer"; + + @BuildStep + void collectInterceptedStaticMethodsCandidates(BeanArchiveIndexBuildItem beanArchiveIndex, + BuildProducer interceptedStaticMethods, BeanRegistrationPhaseBuildItem phase, + // Make sure this step is executed in the correct order + BuildProducer configurators) { + + // In this step we collect all intercepted static methods, ie. static methods annotated with interceptor bindings + IndexView index = beanArchiveIndex.getIndex(); + BeanDeployment beanDeployment = phase.getBeanProcessor().getBeanDeployment(); + InterceptorResolver interceptorResolver = beanDeployment.getInterceptorResolver(); + Set interceptorBindingNames = beanDeployment.getInterceptorBindings().stream().map(ClassInfo::name) + .collect(Collectors.toSet()); + + for (ClassInfo clazz : index.getKnownClasses()) { + for (MethodInfo method : clazz.methods()) { + if (Modifier.isStatic(method.flags()) && !"clinit".equals(method.name())) { + Collection annotations = beanDeployment.getAnnotations(method); + if (!annotations.isEmpty()) { + // Only method-level bindings are considered due to backwards compatibility + Set methodLevelBindings = new HashSet<>(); + for (AnnotationInstance annotationInstance : annotations) { + if (annotationInstance.target().kind() == Kind.METHOD + && interceptorBindingNames.contains(annotationInstance.name())) { + methodLevelBindings.add(annotationInstance); + } + } + if (!methodLevelBindings.isEmpty()) { + List interceptors = interceptorResolver.resolve(InterceptionType.AROUND_INVOKE, + methodLevelBindings); + if (!interceptors.isEmpty()) { + LOGGER.infof("Intercepted static method found on %s: %s", method.declaringClass().name(), + method); + interceptedStaticMethods.produce( + new InterceptedStaticMethodBuildItem(method, methodLevelBindings, interceptors)); + } + } + } + } + } + } + } + + @BuildStep + void processInterceptedStaticMethods(BeanArchiveIndexBuildItem beanArchiveIndex, + BeanRegistrationPhaseBuildItem phase, + List interceptedStaticMethods, + BuildProducer generatedClasses, + BuildProducer transformers, + BuildProducer reflectiveMethods) { + + if (interceptedStaticMethods.isEmpty()) { + return; + } + + ClassOutput classOutput = new GeneratedClassGizmoAdaptor(generatedClasses, true); + + // declaring class -> intercepted static methods + Map> interceptedStaticMethodsMap = new HashMap<>(); + for (InterceptedStaticMethodBuildItem interceptedStaticMethod : interceptedStaticMethods) { + List list = interceptedStaticMethodsMap + .get(interceptedStaticMethod.getTarget().name()); + if (list == null) { + list = new ArrayList<>(); + interceptedStaticMethodsMap.put(interceptedStaticMethod.getTarget().name(), list); + } + list.add(interceptedStaticMethod); + } + + // For each declaring class create an initializer class that: + // 1. registers all interceptor chains inside an "init_static_intercepted_methods" method + // 2. adds static methods to invoke the interceptor chain and delegate to the copy of the original static method + // declaring class -> initializer class + Map initializers = new HashMap<>(); + String initAllMethodName = "init_static_intercepted_methods"; + for (Entry> entry : interceptedStaticMethodsMap.entrySet()) { + + String packageName = DotNames.packageName(entry.getKey()); + String intializerName = packageName + "." + entry.getKey().withoutPackagePrefix() + INITIALIZER_CLASS_SUFFIX; + initializers.put(entry.getKey(), intializerName); + + ClassCreator initializer = ClassCreator.builder().classOutput(classOutput) + .className(intializerName.replace('.', '/')).setFinal(true).build(); + + List initMethods = new ArrayList<>(); + for (InterceptedStaticMethodBuildItem interceptedStaticMethod : entry.getValue()) { + initMethods.add(implementInit(beanArchiveIndex.getIndex(), classOutput, initializer, interceptedStaticMethod, + reflectiveMethods, phase.getBeanProcessor())); + implementForward(initializer, interceptedStaticMethod); + } + + MethodCreator init = initializer.getMethodCreator(initAllMethodName, void.class) + .setModifiers(ACC_PUBLIC | ACC_STATIC); + for (String initMethod : initMethods) { + init.invokeStaticMethod( + MethodDescriptor.ofMethod(initializer.getClassName(), initMethod, void.class)); + + } + init.returnValue(null); + initializer.close(); + } + + // Transform all declaring classes + // For each intercepted static methods create a copy and modify the original method to delegate to the relevant initializer + for (Entry> entry : interceptedStaticMethodsMap.entrySet()) { + transformers.produce(new BytecodeTransformerBuildItem(entry.getKey().toString(), + new InterceptedStaticMethodsEnhancer(initializers.get(entry.getKey()), entry.getValue()))); + } + + // Generate a global initializer that calls all other initializers + ClassCreator globalInitializer = ClassCreator.builder().classOutput(classOutput) + .className(InterceptedStaticMethodsRecorder.INTIALIZER_CLASS_NAME.replace('.', '/')).setFinal(true).build(); + + MethodCreator staticInit = globalInitializer.getMethodCreator("", void.class) + .setModifiers(ACC_STATIC); + for (String initializerClass : initializers.values()) { + staticInit.invokeStaticMethod( + MethodDescriptor.ofMethod(initializerClass, initAllMethodName, void.class)); + } + staticInit.returnValue(null); + globalInitializer.close(); + } + + @Record(STATIC_INIT) + @BuildStep + void callInitializer(BeanContainerBuildItem beanContainer, List interceptedStaticMethods, + InterceptedStaticMethodsRecorder recorder) { + if (interceptedStaticMethods.isEmpty()) { + return; + } + recorder.callInitializer(); + } + + private void implementForward(ClassCreator initializer, + InterceptedStaticMethodBuildItem interceptedStaticMethod) { + MethodInfo method = interceptedStaticMethod.getMethod(); + List params = method.parameters(); + Object[] paramTypes = new String[params.size()]; + for (int i = 0; i < paramTypes.length; ++i) { + paramTypes[i] = DescriptorUtils.typeToString(params.get(i)); + } + MethodCreator forward = initializer + .getMethodCreator(interceptedStaticMethod.getHash(), DescriptorUtils.typeToString(method.returnType()), + paramTypes) + .setModifiers(ACC_PUBLIC | ACC_FINAL | ACC_STATIC); + ResultHandle argArray = forward.newArray(Object.class, params.size()); + for (int i = 0; i < params.size(); i++) { + forward.writeArrayValue(argArray, i, forward.getMethodParam(i)); + } + ResultHandle ret = forward.invokeStaticMethod(INTERCEPTED_STATIC_METHODS_AROUND_INVOKE, + forward.load(interceptedStaticMethod.getHash()), argArray); + forward.returnValue(ret); + } + + private String implementInit(IndexView index, ClassOutput classOutput, ClassCreator initializer, + InterceptedStaticMethodBuildItem interceptedStaticMethod, + BuildProducer reflectiveMethods, BeanProcessor beanProcessor) { + + MethodInfo method = interceptedStaticMethod.getMethod(); + List interceptors = interceptedStaticMethod.getInterceptors(); + Set bindings = interceptedStaticMethod.getBindings(); + + // init_interceptMe_hash() + String name = new StringBuilder("init") + .append("_") + .append(method.name()) + .append("_") + .append(interceptedStaticMethod.getHash()).toString(); + + MethodCreator init = initializer.getMethodCreator(name, void.class) + .setModifiers(ACC_PRIVATE | ACC_FINAL | ACC_STATIC); + ResultHandle creationalContext = init.newInstance( + MethodDescriptor.ofConstructor(CreationalContextImpl.class, Contextual.class), init.loadNull()); + + // 1. Interceptor chain + ResultHandle chainHandle; + if (interceptors.size() == 1) { + // List m1Chain = Collections.singletonList(...); + chainHandle = init.invokeStaticMethod(MethodDescriptors.COLLECTIONS_SINGLETON_LIST, + createInterceptorInvocation(interceptors.get(0), init, creationalContext)); + } else { + // List m1Chain = new ArrayList<>(); + chainHandle = init.newInstance(MethodDescriptor.ofConstructor(ArrayList.class)); + for (InterceptorInfo interceptor : interceptors) { + // m1Chain.add(InvocationContextImpl.InterceptorInvocation.aroundInvoke(p3,interceptorInstanceMap.get(InjectableInterceptor.getIdentifier()))) + init.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, chainHandle, + createInterceptorInvocation(interceptor, init, creationalContext)); + } + } + + // 2. Method method = Reflections.findMethod(...) + ResultHandle[] paramsHandles = new ResultHandle[3]; + paramsHandles[0] = init.loadClass(method.declaringClass().name().toString()); + paramsHandles[1] = init.load(method.name()); + if (!method.parameters().isEmpty()) { + ResultHandle paramsArray = init.newArray(Class.class, init.load(method.parameters().size())); + for (ListIterator iterator = method.parameters().listIterator(); iterator.hasNext();) { + init.writeArrayValue(paramsArray, iterator.nextIndex(), + init.loadClass(iterator.next().name().toString())); + } + paramsHandles[2] = paramsArray; + } else { + paramsHandles[2] = init.newArray(Class.class, init.load(0)); + } + ResultHandle methodHandle = init.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_METHOD, + paramsHandles); + + // 3. Interceptor bindings + ResultHandle bindingsHandle; + if (bindings.size() == 1) { + bindingsHandle = init.invokeStaticMethod(MethodDescriptors.COLLECTIONS_SINGLETON, + createBindingLiteral(index, classOutput, init, bindings.iterator().next(), + beanProcessor.getAnnotationLiteralProcessor())); + } else { + bindingsHandle = init.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + for (AnnotationInstance binding : bindings) { + init.invokeInterfaceMethod(MethodDescriptors.SET_ADD, bindingsHandle, + createBindingLiteral(index, classOutput, init, binding, beanProcessor.getAnnotationLiteralProcessor())); + } + } + + // Now create metadata for the given intercepted method + ResultHandle metadataHandle = init.newInstance(MethodDescriptors.INTERCEPTED_METHOD_METADATA_CONSTRUCTOR, + chainHandle, methodHandle, bindingsHandle); + + // Needed when running on native image + reflectiveMethods.produce(new ReflectiveMethodBuildItem(method)); + + // Create forwarding function + ResultHandle forwardingFunc = createForwardingFunction(init, interceptedStaticMethod.getTarget(), method); + + ResultHandle staticMethodHandle = init.newInstance( + MethodDescriptor.ofConstructor(StaticMethod.class, Function.class, InterceptedMethodMetadata.class), + forwardingFunc, metadataHandle); + + // Call StaticMethodsInterceptors.register() + init.invokeStaticMethod(INTERCEPTED_STATIC_METHODS_REGISTER, init.load(interceptedStaticMethod.getHash()), + staticMethodHandle); + init.returnValue(null); + return name; + } + + private ResultHandle createBindingLiteral(IndexView index, ClassOutput classOutput, BytecodeCreator init, + AnnotationInstance binding, AnnotationLiteralProcessor annotationLiteralProcessor) { + ClassInfo bindingClass = index.getClassByName(binding.name()); + return annotationLiteralProcessor.process(init, classOutput, bindingClass, binding, + "io.quarkus.arc.runtime"); + } + + private ResultHandle createInterceptorInvocation(InterceptorInfo interceptor, BytecodeCreator init, + ResultHandle creationalContext) { + ResultHandle interceptorBean = getInterceptorBean(interceptor, init); + ResultHandle interceptorInstane = createInterceptor(interceptorBean, init, creationalContext); + return init.invokeStaticMethod( + MethodDescriptors.INTERCEPTOR_INVOCATION_AROUND_INVOKE, + interceptorBean, interceptorInstane); + } + + private ResultHandle getInterceptorBean(InterceptorInfo interceptor, BytecodeCreator creator) { + ResultHandle containerHandle = creator + .invokeStaticMethod(MethodDescriptors.ARC_CONTAINER); + return creator.checkCast(creator.invokeInterfaceMethod( + MethodDescriptors.ARC_CONTAINER_BEAN, + containerHandle, creator.load(interceptor.getIdentifier())), InjectableInterceptor.class); + } + + private ResultHandle createInterceptor(ResultHandle interceptorBean, BytecodeCreator creator, + ResultHandle parentCreationalContext) { + ResultHandle creationalContext = creator.invokeStaticMethod(MethodDescriptors.CREATIONAL_CTX_CHILD, + parentCreationalContext); + return creator.invokeInterfaceMethod( + MethodDescriptors.INJECTABLE_REF_PROVIDER_GET, interceptorBean, creationalContext); + } + + private ResultHandle createForwardingFunction(MethodCreator init, ClassInfo target, MethodInfo method) { + // Forwarding function + // Function forward = ctx -> Foo.interceptMe_original((java.lang.String)ctx.getParameters()[0]) + FunctionCreator func = init.createFunction(Function.class); + BytecodeCreator funcBytecode = func.getBytecode(); + List paramTypes = method.parameters(); + ResultHandle[] paramHandles; + String[] params; + if (paramTypes.isEmpty()) { + paramHandles = new ResultHandle[0]; + params = new String[0]; + } else { + paramHandles = new ResultHandle[paramTypes.size()]; + ResultHandle ctxHandle = funcBytecode.getMethodParam(0); + ResultHandle ctxParamsHandle = funcBytecode.invokeInterfaceMethod( + MethodDescriptor.ofMethod(InvocationContext.class, "getParameters", Object[].class), + ctxHandle); + // autoboxing is handled inside Gizmo + for (int i = 0; i < paramHandles.length; i++) { + paramHandles[i] = funcBytecode.readArrayValue(ctxParamsHandle, i); + } + params = new String[paramTypes.size()]; + for (int i = 0; i < paramTypes.size(); i++) { + params[i] = paramTypes.get(i).name().toString(); + } + } + ResultHandle ret = funcBytecode.invokeStaticMethod( + MethodDescriptor.ofMethod(target.name().toString(), method.name() + ORGINAL_METHOD_COPY_SUFFIX, + method.returnType().name().toString(), + params), + paramHandles); + if (ret == null) { + funcBytecode.returnValue(funcBytecode.loadNull()); + } else { + funcBytecode.returnValue(ret); + } + return func.getInstance(); + } + + static class InterceptedStaticMethodsEnhancer implements BiFunction { + + private final String initializerClassName; + private final List methods; + + public InterceptedStaticMethodsEnhancer(String initializerClassName, List methods) { + this.methods = methods; + this.initializerClassName = initializerClassName; + } + + @Override + public ClassVisitor apply(String className, ClassVisitor outputClassVisitor) { + return new InterceptedStaticMethodsClassVisitor(initializerClassName, outputClassVisitor, methods); + } + + } + + static class InterceptedStaticMethodsClassVisitor extends ClassVisitor { + + private final String initializerClassName; + private final List methods; + + public InterceptedStaticMethodsClassVisitor(String initializerClassName, ClassVisitor outputClassVisitor, + List methods) { + super(Gizmo.ASM_API_VERSION, outputClassVisitor); + this.methods = methods; + this.initializerClassName = initializerClassName; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + InterceptedStaticMethodBuildItem method = findMatchingMethod(access, name, descriptor); + if (method != null) { + MethodVisitor copy = super.visitMethod(access, + name + ORGINAL_METHOD_COPY_SUFFIX, + descriptor, + signature, + exceptions); + MethodVisitor superVisitor = super.visitMethod(access, name, descriptor, signature, exceptions); + return new InterceptedStaticMethodsMethodVisitor(superVisitor, copy, initializerClassName, method); + } else { + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + } + + private InterceptedStaticMethodBuildItem findMatchingMethod(int access, String name, String descriptor) { + if (Modifier.isStatic(access)) { + for (InterceptedStaticMethodBuildItem method : methods) { + if (method.getMethod().name().equals(name) + && MethodDescriptor.of(method.getMethod()).getDescriptor().equals(descriptor)) { + return method; + } + } + } + return null; + } + + } + + static class InterceptedStaticMethodsMethodVisitor extends MethodVisitor { + + private final String initializerClassName; + private final InterceptedStaticMethodBuildItem interceptedStaticMethod; + private final MethodVisitor superVisitor; + + public InterceptedStaticMethodsMethodVisitor(MethodVisitor superVisitor, MethodVisitor copyVisitor, + String initializerClassName, InterceptedStaticMethodBuildItem interceptedStaticMethod) { + super(Gizmo.ASM_API_VERSION, copyVisitor); + this.superVisitor = superVisitor; + this.initializerClassName = initializerClassName; + this.interceptedStaticMethod = interceptedStaticMethod; + } + + @Override + public void visitEnd() { + // Invoke the initializer, i.e. Foo_InterceptorInitializer.hash("ping") + MethodDescriptor descriptor = MethodDescriptor.of(interceptedStaticMethod.getMethod()); + int idx = 0; + for (String paramType : descriptor.getParameterTypes()) { + // Load params on the stack + superVisitor.visitIntInsn(getOpCode(paramType), idx++); + } + superVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, + initializerClassName.replace('.', '/'), interceptedStaticMethod.getHash(), + descriptor.getDescriptor().toString(), + false); + // TODO void methods? + superVisitor.visitInsn(getReturnInstruction(descriptor.getReturnType())); + superVisitor.visitMaxs(0, 0); + superVisitor.visitEnd(); + + super.visitEnd(); + } + + private int getOpCode(String type) { + switch (type) { + case "Z": + case "B": + case "C": + case "S": + case "I": + return Opcodes.ILOAD; + case "J": + return Opcodes.LLOAD; + case "F": + return Opcodes.FLOAD; + case "D": + return Opcodes.DLOAD; + default: + return Opcodes.ALOAD; + } + } + + int getReturnInstruction(String type) { + switch (type) { + case "Z": + case "B": + case "C": + case "S": + case "I": + return Opcodes.IRETURN; + case "J": + return Opcodes.LRETURN; + case "F": + return Opcodes.FRETURN; + case "D": + return Opcodes.DRETURN; + case "V": + return Opcodes.RETURN; + default: + return Opcodes.ARETURN; + } + } + + } + +} diff --git a/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/interceptor/staticmethods/InterceptedStaticMethodTest.java b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/interceptor/staticmethods/InterceptedStaticMethodTest.java new file mode 100644 index 00000000000000..1acd606c6faeec --- /dev/null +++ b/extensions/arc/deployment/src/test/java/io/quarkus/arc/test/interceptor/staticmethods/InterceptedStaticMethodTest.java @@ -0,0 +1,79 @@ +package io.quarkus.arc.test.interceptor.staticmethods; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; +import java.util.concurrent.atomic.AtomicInteger; + +import javax.annotation.Priority; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InterceptorBinding; +import javax.interceptor.InvocationContext; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusUnitTest; + +public class InterceptedStaticMethodTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(InterceptMe.class, Simple.class, SimpleInterceptor.class)); + + @Test + public void testInterceptor() { + assertEquals("OK:PONG", Simple.ping("pong")); + Simple.pong(); + assertEquals(1, SimpleInterceptor.VOID_INTERCEPTIONS.get()); + } + + public static class Simple { + + @InterceptMe + public static String ping(String val) { + return val.toUpperCase(); + } + + @InterceptMe + static void pong() { + } + + } + + @Priority(1) + @Interceptor + @InterceptMe + static class SimpleInterceptor { + + static final AtomicInteger VOID_INTERCEPTIONS = new AtomicInteger(); + + @AroundInvoke + Object aroundInvoke(InvocationContext ctx) throws Exception { + Object ret = ctx.proceed(); + if (ret != null) { + return "OK:" + ctx.proceed(); + } else { + VOID_INTERCEPTIONS.incrementAndGet(); + return ret; + } + } + + } + + @InterceptorBinding + @Target({ TYPE, METHOD }) + @Retention(RUNTIME) + @interface InterceptMe { + + } + +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java new file mode 100644 index 00000000000000..f15d306ae204a7 --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptedStaticMethodsRecorder.java @@ -0,0 +1,22 @@ +package io.quarkus.arc.runtime; + +import io.quarkus.runtime.annotations.Recorder; + +@Recorder +public class InterceptedStaticMethodsRecorder { + + public static final String INTIALIZER_CLASS_NAME = "io.quarkus.arc.runtime.InterceptedStaticMethodsInitializer"; + + public void callInitializer() { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + if (cl == null) { + cl = InterceptedStaticMethodsRecorder.class.getClassLoader(); + } + try { + Class.forName(INTIALIZER_CLASS_NAME, true, cl); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + } + +} diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/interceptor/TransactionalInterceptorBase.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/interceptor/TransactionalInterceptorBase.java index 669975b10aa1af..0fcfc69c20f5bd 100644 --- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/interceptor/TransactionalInterceptorBase.java +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/interceptor/TransactionalInterceptorBase.java @@ -83,7 +83,11 @@ private Transactional getTransactional(InvocationContext ic) { private TransactionConfiguration getTransactionConfiguration(InvocationContext ic) { TransactionConfiguration configuration = ic.getMethod().getAnnotation(TransactionConfiguration.class); if (configuration == null) { - return ic.getTarget().getClass().getAnnotation(TransactionConfiguration.class); + Object target = ic.getTarget(); + if (target == null) { + return null; + } + return target.getClass().getAnnotation(TransactionConfiguration.class); } return configuration; } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java index 2416bda6b16e07..b5634be6a25c1a 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AnnotationLiteralProcessor.java @@ -19,10 +19,11 @@ import org.jboss.jandex.MethodInfo; /** - * - * @author Martin Kouba + * Generates shared annotation literal classes that can be used to represent annotation instances at runtime. + *

+ * This construct is thread-safe. */ -class AnnotationLiteralProcessor { +public class AnnotationLiteralProcessor { private final ComputingCache cache; @@ -54,7 +55,7 @@ ComputingCache getCache() { * @param targetPackage Target package is only used if annotation literals are not shared * @return an annotation literal result handle */ - ResultHandle process(BytecodeCreator bytecode, ClassOutput classOutput, ClassInfo annotationClass, + public ResultHandle process(BytecodeCreator bytecode, ClassOutput classOutput, ClassInfo annotationClass, AnnotationInstance annotationInstance, String targetPackage) { Objects.requireNonNull(annotationClass, "Annotation class not available: " + annotationInstance); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index af5692eb16813c..62c1c0c0cbaa80 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -350,6 +350,10 @@ public Collection getQualifiers() { return Collections.unmodifiableCollection(qualifiers.values()); } + public Collection getInterceptorBindings() { + return Collections.unmodifiableCollection(interceptorBindings.values()); + } + public Collection getObservers() { return observers; } @@ -366,7 +370,7 @@ BeanResolver getBeanResolver() { return beanResolver; } - InterceptorResolver getInterceptorResolver() { + public InterceptorResolver getInterceptorResolver() { return interceptorResolver; } @@ -422,7 +426,7 @@ AnnotationStore getAnnotationStore() { return annotationStore; } - Collection getAnnotations(AnnotationTarget target) { + public Collection getAnnotations(AnnotationTarget target) { return annotationStore.getAnnotations(target); } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java index 6cbc908cdfa962..d8a8b47a9e0110 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java @@ -52,7 +52,7 @@ public static Builder builder() { private final String name; private final ResourceOutput output; - private final boolean sharedAnnotationLiterals; + private final AnnotationLiteralProcessor annotationLiterals; private final ReflectionRegistration reflectionRegistration; private final List beanRegistrars; private final List contextRegistrars; @@ -95,7 +95,7 @@ private BeanProcessor(String name, IndexView index, Collection generateResources(ReflectionRegistration reflectionRegistr Map beanToGeneratedName = new HashMap<>(); Map observerToGeneratedName = new HashMap<>(); - AnnotationLiteralProcessor annotationLiterals = new AnnotationLiteralProcessor(sharedAnnotationLiterals, - applicationClassPredicate); BeanGenerator beanGenerator = new BeanGenerator(annotationLiterals, applicationClassPredicate, privateMembers, generateSources, reflectionRegistration, existingClasses, beanToGeneratedName, injectionPointAnnotationsPredicate); @@ -245,6 +243,10 @@ public BeanDeployment getBeanDeployment() { return beanDeployment; } + public AnnotationLiteralProcessor getAnnotationLiteralProcessor() { + return annotationLiterals; + } + public BeanDeployment process() throws IOException { Consumer unsupportedBytecodeTransformer = new Consumer() { @Override diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java index 58d1e447291f64..063184a86e5d1d 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InterceptorInfo.java @@ -16,7 +16,7 @@ * * @author Martin Kouba */ -class InterceptorInfo extends BeanInfo implements Comparable { +public class InterceptorInfo extends BeanInfo implements Comparable { private final Set bindings; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java index 6864271056f58f..e6abd8cb444333 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/MethodDescriptors.java @@ -9,11 +9,11 @@ import io.quarkus.arc.InjectableReferenceProvider; import io.quarkus.arc.impl.CreationalContextImpl; import io.quarkus.arc.impl.FixedValueSupplier; +import io.quarkus.arc.impl.InterceptedMethodMetadata; import io.quarkus.arc.impl.InterceptorInvocation; import io.quarkus.arc.impl.InvocationContexts; import io.quarkus.arc.impl.MapValueSupplier; import io.quarkus.arc.impl.Reflections; -import io.quarkus.arc.impl.SubclassMethodMetadata; import io.quarkus.gizmo.MethodDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -35,177 +35,195 @@ * * @author Martin Kouba */ -final class MethodDescriptors { +public final class MethodDescriptors { - static final MethodDescriptor FIXED_VALUE_SUPPLIER_CONSTRUCTOR = MethodDescriptor.ofConstructor(FixedValueSupplier.class, + public static final MethodDescriptor FIXED_VALUE_SUPPLIER_CONSTRUCTOR = MethodDescriptor.ofConstructor( + FixedValueSupplier.class, Object.class); - static final MethodDescriptor MAP_VALUE_SUPPLIER_CONSTRUCTOR = MethodDescriptor.ofConstructor(MapValueSupplier.class, + public static final MethodDescriptor MAP_VALUE_SUPPLIER_CONSTRUCTOR = MethodDescriptor.ofConstructor(MapValueSupplier.class, Map.class, String.class); - static final MethodDescriptor SUPPLIER_GET = MethodDescriptor.ofMethod(Supplier.class, "get", Object.class); + public static final MethodDescriptor SUPPLIER_GET = MethodDescriptor.ofMethod(Supplier.class, "get", Object.class); - static final MethodDescriptor CREATIONAL_CTX_CHILD = MethodDescriptor.ofMethod(CreationalContextImpl.class, "child", + public static final MethodDescriptor CREATIONAL_CTX_CHILD = MethodDescriptor.ofMethod(CreationalContextImpl.class, "child", CreationalContextImpl.class, CreationalContext.class); - static final MethodDescriptor CREATIONAL_CTX_CHILD_CONTEXTUAL = MethodDescriptor.ofMethod(CreationalContextImpl.class, + public static final MethodDescriptor CREATIONAL_CTX_CHILD_CONTEXTUAL = MethodDescriptor.ofMethod( + CreationalContextImpl.class, "child", CreationalContextImpl.class, InjectableReferenceProvider.class, CreationalContext.class); - static final MethodDescriptor MAP_GET = MethodDescriptor.ofMethod(Map.class, "get", Object.class, Object.class); + public static final MethodDescriptor MAP_GET = MethodDescriptor.ofMethod(Map.class, "get", Object.class, Object.class); - static final MethodDescriptor MAP_PUT = MethodDescriptor.ofMethod(Map.class, "put", Object.class, Object.class, + public static final MethodDescriptor MAP_PUT = MethodDescriptor.ofMethod(Map.class, "put", Object.class, Object.class, Object.class); - static final MethodDescriptor INJECTABLE_REF_PROVIDER_GET = MethodDescriptor.ofMethod(InjectableReferenceProvider.class, + public static final MethodDescriptor INJECTABLE_REF_PROVIDER_GET = MethodDescriptor.ofMethod( + InjectableReferenceProvider.class, "get", Object.class, CreationalContext.class); - static final MethodDescriptor SET_ADD = MethodDescriptor.ofMethod(Set.class, "add", boolean.class, Object.class); + public static final MethodDescriptor SET_ADD = MethodDescriptor.ofMethod(Set.class, "add", boolean.class, Object.class); - static final MethodDescriptor LIST_ADD = MethodDescriptor.ofMethod(List.class, "add", boolean.class, Object.class); + public static final MethodDescriptor LIST_ADD = MethodDescriptor.ofMethod(List.class, "add", boolean.class, Object.class); - static final MethodDescriptor OBJECT_EQUALS = MethodDescriptor.ofMethod(Object.class, "equals", boolean.class, + public static final MethodDescriptor OBJECT_EQUALS = MethodDescriptor.ofMethod(Object.class, "equals", boolean.class, Object.class); - static final MethodDescriptor OBJECT_TO_STRING = MethodDescriptor.ofMethod(Object.class, "toString", String.class); + public static final MethodDescriptor OBJECT_TO_STRING = MethodDescriptor.ofMethod(Object.class, "toString", String.class); - static final MethodDescriptor OBJECT_CONSTRUCTOR = MethodDescriptor.ofConstructor(Object.class); + public static final MethodDescriptor OBJECT_CONSTRUCTOR = MethodDescriptor.ofConstructor(Object.class); - static final MethodDescriptor INTERCEPTOR_INVOCATION_POST_CONSTRUCT = MethodDescriptor.ofMethod(InterceptorInvocation.class, + public static final MethodDescriptor INTERCEPTOR_INVOCATION_POST_CONSTRUCT = MethodDescriptor.ofMethod( + InterceptorInvocation.class, "postConstruct", InterceptorInvocation.class, InjectableInterceptor.class, Object.class); - static final MethodDescriptor INTERCEPTOR_INVOCATION_PRE_DESTROY = MethodDescriptor.ofMethod(InterceptorInvocation.class, + public static final MethodDescriptor INTERCEPTOR_INVOCATION_PRE_DESTROY = MethodDescriptor.ofMethod( + InterceptorInvocation.class, "preDestroy", InterceptorInvocation.class, InjectableInterceptor.class, Object.class); - static final MethodDescriptor INTERCEPTOR_INVOCATION_AROUND_CONSTRUCT = MethodDescriptor.ofMethod( + public static final MethodDescriptor INTERCEPTOR_INVOCATION_AROUND_CONSTRUCT = MethodDescriptor.ofMethod( InterceptorInvocation.class, "aroundConstruct", InterceptorInvocation.class, InjectableInterceptor.class, Object.class); - static final MethodDescriptor INTERCEPTOR_INVOCATION_AROUND_INVOKE = MethodDescriptor.ofMethod(InterceptorInvocation.class, + public static final MethodDescriptor INTERCEPTOR_INVOCATION_AROUND_INVOKE = MethodDescriptor.ofMethod( + InterceptorInvocation.class, "aroundInvoke", InterceptorInvocation.class, InjectableInterceptor.class, Object.class); - static final MethodDescriptor REFLECTIONS_FIND_CONSTRUCTOR = MethodDescriptor.ofMethod(Reflections.class, "findConstructor", + public static final MethodDescriptor REFLECTIONS_FIND_CONSTRUCTOR = MethodDescriptor.ofMethod(Reflections.class, + "findConstructor", Constructor.class, Class.class, Class[].class); - static final MethodDescriptor REFLECTIONS_FIND_METHOD = MethodDescriptor.ofMethod(Reflections.class, "findMethod", + public static final MethodDescriptor REFLECTIONS_FIND_METHOD = MethodDescriptor.ofMethod(Reflections.class, "findMethod", Method.class, Class.class, String.class, Class[].class); - static final MethodDescriptor REFLECTIONS_FIND_FIELD = MethodDescriptor.ofMethod(Reflections.class, "findField", + public static final MethodDescriptor REFLECTIONS_FIND_FIELD = MethodDescriptor.ofMethod(Reflections.class, "findField", Field.class, Class.class, String.class); - static final MethodDescriptor REFLECTIONS_WRITE_FIELD = MethodDescriptor.ofMethod(Reflections.class, "writeField", + public static final MethodDescriptor REFLECTIONS_WRITE_FIELD = MethodDescriptor.ofMethod(Reflections.class, "writeField", void.class, Class.class, String.class, Object.class, Object.class); - static final MethodDescriptor REFLECTIONS_READ_FIELD = MethodDescriptor.ofMethod(Reflections.class, "readField", + public static final MethodDescriptor REFLECTIONS_READ_FIELD = MethodDescriptor.ofMethod(Reflections.class, "readField", Object.class, Class.class, String.class, Object.class); - static final MethodDescriptor REFLECTIONS_INVOKE_METHOD = MethodDescriptor.ofMethod(Reflections.class, "invokeMethod", + public static final MethodDescriptor REFLECTIONS_INVOKE_METHOD = MethodDescriptor.ofMethod(Reflections.class, + "invokeMethod", Object.class, Class.class, String.class, Class[].class, Object.class, Object[].class); - static final MethodDescriptor REFLECTIONS_NEW_INSTANCE = MethodDescriptor.ofMethod(Reflections.class, "newInstance", + public static final MethodDescriptor REFLECTIONS_NEW_INSTANCE = MethodDescriptor.ofMethod(Reflections.class, "newInstance", Object.class, Class.class, Class[].class, Object[].class); - static final MethodDescriptor CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE = MethodDescriptor.ofMethod(ClientProxy.class, + public static final MethodDescriptor CLIENT_PROXY_GET_CONTEXTUAL_INSTANCE = MethodDescriptor.ofMethod(ClientProxy.class, ClientProxyGenerator.GET_CONTEXTUAL_INSTANCE_METHOD_NAME, Object.class); - static final MethodDescriptor INJECTABLE_BEAN_DESTROY = MethodDescriptor.ofMethod(InjectableBean.class, "destroy", + public static final MethodDescriptor INJECTABLE_BEAN_DESTROY = MethodDescriptor.ofMethod(InjectableBean.class, "destroy", void.class, Object.class, CreationalContext.class); - static final MethodDescriptor CREATIONAL_CTX_RELEASE = MethodDescriptor.ofMethod(CreationalContext.class, "release", + public static final MethodDescriptor CREATIONAL_CTX_RELEASE = MethodDescriptor.ofMethod(CreationalContext.class, "release", void.class); - static final MethodDescriptor EVENT_CONTEXT_GET_EVENT = MethodDescriptor.ofMethod(EventContext.class, "getEvent", + public static final MethodDescriptor EVENT_CONTEXT_GET_EVENT = MethodDescriptor.ofMethod(EventContext.class, "getEvent", Object.class); - static final MethodDescriptor EVENT_CONTEXT_GET_METADATA = MethodDescriptor.ofMethod(EventContext.class, "getMetadata", + public static final MethodDescriptor EVENT_CONTEXT_GET_METADATA = MethodDescriptor.ofMethod(EventContext.class, + "getMetadata", EventMetadata.class); - static final MethodDescriptor INVOCATION_CONTEXTS_PERFORM_AROUND_INVOKE = MethodDescriptor.ofMethod( + public static final MethodDescriptor INVOCATION_CONTEXTS_PERFORM_AROUND_INVOKE = MethodDescriptor.ofMethod( InvocationContexts.class, "performAroundInvoke", Object.class, Object.class, Method.class, Function.class, Object[].class, List.class, Set.class); - static final MethodDescriptor INVOCATION_CONTEXTS_AROUND_CONSTRUCT = MethodDescriptor.ofMethod( + public static final MethodDescriptor INVOCATION_CONTEXTS_AROUND_CONSTRUCT = MethodDescriptor.ofMethod( InvocationContexts.class, "aroundConstruct", InvocationContext.class, Constructor.class, List.class, Supplier.class, Set.class); - static final MethodDescriptor INVOCATION_CONTEXTS_POST_CONSTRUCT = MethodDescriptor.ofMethod( + public static final MethodDescriptor INVOCATION_CONTEXTS_POST_CONSTRUCT = MethodDescriptor.ofMethod( InvocationContexts.class, "postConstruct", InvocationContext.class, Object.class, List.class, Set.class); - static final MethodDescriptor INVOCATION_CONTEXTS_PRE_DESTROY = MethodDescriptor.ofMethod(InvocationContexts.class, + public static final MethodDescriptor INVOCATION_CONTEXTS_PRE_DESTROY = MethodDescriptor.ofMethod(InvocationContexts.class, "preDestroy", InvocationContext.class, Object.class, List.class, Set.class); - static final MethodDescriptor INVOCATION_CONTEXT_PROCEED = MethodDescriptor.ofMethod(InvocationContext.class, "proceed", + public static final MethodDescriptor INVOCATION_CONTEXT_PROCEED = MethodDescriptor.ofMethod(InvocationContext.class, + "proceed", Object.class); - static final MethodDescriptor INVOCATION_CONTEXT_GET_TARGET = MethodDescriptor.ofMethod(InvocationContext.class, + public static final MethodDescriptor INVOCATION_CONTEXT_GET_TARGET = MethodDescriptor.ofMethod(InvocationContext.class, "getTarget", Object.class); - static final MethodDescriptor CREATIONAL_CTX_ADD_DEP_TO_PARENT = MethodDescriptor.ofMethod(CreationalContextImpl.class, + public static final MethodDescriptor CREATIONAL_CTX_ADD_DEP_TO_PARENT = MethodDescriptor.ofMethod( + CreationalContextImpl.class, "addDependencyToParent", void.class, InjectableBean.class, Object.class, CreationalContext.class); - static final MethodDescriptor COLLECTIONS_UNMODIFIABLE_SET = MethodDescriptor.ofMethod(Collections.class, "unmodifiableSet", + public static final MethodDescriptor COLLECTIONS_UNMODIFIABLE_SET = MethodDescriptor.ofMethod(Collections.class, + "unmodifiableSet", Set.class, Set.class); - static final MethodDescriptor COLLECTIONS_SINGLETON = MethodDescriptor.ofMethod(Collections.class, "singleton", + public static final MethodDescriptor COLLECTIONS_SINGLETON = MethodDescriptor.ofMethod(Collections.class, "singleton", Set.class, Object.class); - static final MethodDescriptor COLLECTIONS_SINGLETON_LIST = MethodDescriptor.ofMethod(Collections.class, "singletonList", + public static final MethodDescriptor COLLECTIONS_SINGLETON_LIST = MethodDescriptor.ofMethod(Collections.class, + "singletonList", List.class, Object.class); - static final MethodDescriptor COLLECTIONS_EMPTY_MAP = MethodDescriptor.ofMethod(Collections.class, "emptyMap", + public static final MethodDescriptor COLLECTIONS_EMPTY_MAP = MethodDescriptor.ofMethod(Collections.class, "emptyMap", Map.class); - static final MethodDescriptor ARC_CONTAINER = MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class); + public static final MethodDescriptor ARC_CONTAINER = MethodDescriptor.ofMethod(Arc.class, "container", ArcContainer.class); + + public static final MethodDescriptor ARC_CONTAINER_BEAN = MethodDescriptor.ofMethod(ArcContainer.class, "bean", + InjectableBean.class, String.class); - static final MethodDescriptor ARC_CONTAINER_GET_ACTIVE_CONTEXT = MethodDescriptor.ofMethod(ArcContainer.class, + public static final MethodDescriptor ARC_CONTAINER_GET_ACTIVE_CONTEXT = MethodDescriptor.ofMethod(ArcContainer.class, "getActiveContext", InjectableContext.class, Class.class); - static final MethodDescriptor CONTEXT_GET = MethodDescriptor.ofMethod(Context.class, "get", Object.class, Contextual.class, + public static final MethodDescriptor CONTEXT_GET = MethodDescriptor.ofMethod(Context.class, "get", Object.class, + Contextual.class, CreationalContext.class); - static final MethodDescriptor CONTEXT_GET_IF_PRESENT = MethodDescriptor.ofMethod(Context.class, "get", Object.class, + public static final MethodDescriptor CONTEXT_GET_IF_PRESENT = MethodDescriptor.ofMethod(Context.class, "get", Object.class, Contextual.class); - static final MethodDescriptor GET_IDENTIFIER = MethodDescriptor.ofMethod(InjectableBean.class, "getIdentifier", + public static final MethodDescriptor GET_IDENTIFIER = MethodDescriptor.ofMethod(InjectableBean.class, "getIdentifier", String.class); - static final MethodDescriptor SUBCLASS_METHOD_METADATA_CONSTRUCTOR = MethodDescriptor.ofConstructor( - SubclassMethodMetadata.class, + public static final MethodDescriptor INTERCEPTED_METHOD_METADATA_CONSTRUCTOR = MethodDescriptor.ofConstructor( + InterceptedMethodMetadata.class, List.class, Method.class, Set.class); - static final MethodDescriptor CREATIONAL_CTX_HAS_DEPENDENT_INSTANCES = MethodDescriptor.ofMethod( + public static final MethodDescriptor CREATIONAL_CTX_HAS_DEPENDENT_INSTANCES = MethodDescriptor.ofMethod( CreationalContextImpl.class, "hasDependentInstances", boolean.class); - static final MethodDescriptor THREAD_CURRENT_THREAD = MethodDescriptor.ofMethod(Thread.class, "currentThread", + public static final MethodDescriptor THREAD_CURRENT_THREAD = MethodDescriptor.ofMethod(Thread.class, "currentThread", Thread.class); - static final MethodDescriptor THREAD_GET_TCCL = MethodDescriptor.ofMethod(Thread.class, "getContextClassLoader", + public static final MethodDescriptor THREAD_GET_TCCL = MethodDescriptor.ofMethod(Thread.class, "getContextClassLoader", ClassLoader.class); - static final MethodDescriptor CL_FOR_NAME = MethodDescriptor.ofMethod(Class.class, "forName", Class.class, String.class, + public static final MethodDescriptor CL_FOR_NAME = MethodDescriptor.ofMethod(Class.class, "forName", Class.class, + String.class, boolean.class, ClassLoader.class); private MethodDescriptors() { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java index 8d563e0eff4cad..f53ef50ccccf19 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java @@ -7,7 +7,7 @@ import io.quarkus.arc.ArcUndeclaredThrowableException; import io.quarkus.arc.InjectableInterceptor; import io.quarkus.arc.Subclass; -import io.quarkus.arc.impl.SubclassMethodMetadata; +import io.quarkus.arc.impl.InterceptedMethodMetadata; import io.quarkus.arc.processor.BeanInfo.InterceptionInfo; import io.quarkus.arc.processor.ResourceOutput.Resource; import io.quarkus.gizmo.BytecodeCreator; @@ -63,11 +63,11 @@ public class SubclassGenerator extends AbstractGenerator { protected static final String FIELD_NAME_PREDESTROYS = "preDestroys"; protected static final String FIELD_NAME_METADATA = "metadata"; - protected static final FieldDescriptor FIELD_METADATA_METHOD = FieldDescriptor.of(SubclassMethodMetadata.class, "method", + protected static final FieldDescriptor FIELD_METADATA_METHOD = FieldDescriptor.of(InterceptedMethodMetadata.class, "method", Method.class); - protected static final FieldDescriptor FIELD_METADATA_CHAIN = FieldDescriptor.of(SubclassMethodMetadata.class, "chain", + protected static final FieldDescriptor FIELD_METADATA_CHAIN = FieldDescriptor.of(InterceptedMethodMetadata.class, "chain", List.class); - protected static final FieldDescriptor FIELD_METADATA_BINDINGS = FieldDescriptor.of(SubclassMethodMetadata.class, + protected static final FieldDescriptor FIELD_METADATA_BINDINGS = FieldDescriptor.of(InterceptedMethodMetadata.class, "bindings", Set.class); private final Predicate applicationClassPredicate; @@ -308,8 +308,9 @@ public ResultHandle apply(List interceptors) { ResultHandle bindingsHandle = bindings.computeIfAbsent( interceptedMethod.bindings.stream().map(BindingKey::new).collect(Collectors.toList()), bindingsFun); - //Now create SubclassMethodMetadata for the given intercepted method - ResultHandle methodMetadataHandle = constructor.newInstance(MethodDescriptors.SUBCLASS_METHOD_METADATA_CONSTRUCTOR, + // Now create metadata for the given intercepted method + ResultHandle methodMetadataHandle = constructor.newInstance( + MethodDescriptors.INTERCEPTED_METHOD_METADATA_CONSTRUCTOR, chainHandle, methodHandle, bindingsHandle); // metadata.put("m1", new SubclassMethodMetadata(...)) constructor.invokeInterfaceMethod(MethodDescriptors.MAP_PUT, metadataHandle, methodIdHandle, methodMetadataHandle); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java index 1f8f4517394288..88e7be104bac03 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ArcContainerImpl.java @@ -344,6 +344,7 @@ public synchronized void shutdown() { resolved.clear(); observers.clear(); running.set(false); + InterceptedStaticMethods.clear(); LOGGER.debugf("ArC DI container shut down"); } @@ -455,6 +456,11 @@ private InjectableBean findById(String identifier) { return bean; } } + for (InjectableInterceptor interceptorBean : interceptors) { + if (interceptorBean.getIdentifier().equals(identifier)) { + return interceptorBean; + } + } return null; } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SubclassMethodMetadata.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InterceptedMethodMetadata.java similarity index 70% rename from independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SubclassMethodMetadata.java rename to independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InterceptedMethodMetadata.java index 6295cd045e6c91..58a7c429def0f8 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/SubclassMethodMetadata.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InterceptedMethodMetadata.java @@ -5,13 +5,13 @@ import java.util.List; import java.util.Set; -public class SubclassMethodMetadata { +public class InterceptedMethodMetadata { public final List chain; public final Method method; public final Set bindings; - public SubclassMethodMetadata(List chain, Method method, Set bindings) { + public InterceptedMethodMetadata(List chain, Method method, Set bindings) { this.chain = chain; this.method = method; this.bindings = bindings; diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InterceptedStaticMethods.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InterceptedStaticMethods.java new file mode 100644 index 00000000000000..430d7299d9a17c --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/InterceptedStaticMethods.java @@ -0,0 +1,41 @@ +package io.quarkus.arc.impl; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; +import javax.interceptor.InvocationContext; + +public class InterceptedStaticMethods { + + private static final ConcurrentMap METHODS = new ConcurrentHashMap<>(); + + public static void register(String key, StaticMethod method) { + METHODS.putIfAbsent(key, method); + } + + public static Object aroundInvoke(String key, Object[] args) throws Exception { + StaticMethod method = METHODS.get(key); + if (method == null) { + throw new IllegalArgumentException("Intercepted method metadata not found for key: " + key); + } + return InvocationContexts.performAroundInvoke(null, method.metadata.method, method.forward, args, method.metadata.chain, + method.metadata.bindings); + } + + public static class StaticMethod { + + final Function forward; + final InterceptedMethodMetadata metadata; + + public StaticMethod(Function forward, InterceptedMethodMetadata metadata) { + this.forward = forward; + this.metadata = metadata; + } + + } + + static void clear() { + METHODS.clear(); + } + +} diff --git a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Beer.java b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Beer.java index 8a2e74cf0bc9fb..aa4cace3fa8eea 100644 --- a/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Beer.java +++ b/integration-tests/hibernate-orm-panache/src/main/java/io/quarkus/it/panache/Beer.java @@ -3,9 +3,14 @@ import javax.persistence.Entity; import io.quarkus.hibernate.orm.panache.PanacheEntity; +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; @Entity public class Beer extends PanacheEntity { public String name; + + public static void persist(Object firstEntity, Object... entities) { + PanacheEntityBase.persist(firstEntity, entities); + } } diff --git a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TransactionalPanacheEntityBaseTest.java b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TransactionalPanacheEntityBaseTest.java new file mode 100644 index 00000000000000..1f25ecb8134b1d --- /dev/null +++ b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TransactionalPanacheEntityBaseTest.java @@ -0,0 +1,29 @@ +package io.quarkus.it.panache; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class TransactionalPanacheEntityBaseTest { + + // TODO + // @Test + public void testTransactionalPanacheEntity() { + // Make sure there are no beers stored + Beer.deleteAll(); + Beer b = new Beer(); + b.name = "IPA"; + // Beer.perist() is annotated with @Transactional and should be intercepted + Beer.persist(b); + assertEquals(1, Beer.count()); + } + + public static void main(String[] args) { + Assertions.assertEquals("Byte: 0\nShort: 1\nInt: 2\nLong: 3\nChar: a\nBoolean: true\nFloat: 4.0\nDouble: 5.0", "false"); + } + +} diff --git a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TransactionalRepositoryTest.java b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TransactionalRepositoryTest.java index 10a9f3e5f57986..ff83e310aef86d 100644 --- a/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TransactionalRepositoryTest.java +++ b/integration-tests/hibernate-orm-panache/src/test/java/io/quarkus/it/panache/TransactionalRepositoryTest.java @@ -15,6 +15,9 @@ public class TransactionalRepositoryTest { @Test public void testTransactionalRepository() { + // Make sure there are no beers stored + beerRepository.deleteAll(); + Beer b = new Beer(); b.name = "IPA"; beerRepository.persist(b);