diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptorBindings.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptorBindings.java index c93ee8832a8a0..e5d038d696fa0 100644 --- a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptorBindings.java +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/InterceptorBindings.java @@ -5,17 +5,12 @@ import javax.interceptor.InvocationContext; -import io.quarkus.arc.AroundInvokeInvocationContext; -import io.quarkus.arc.InvocationContextImpl; +import io.quarkus.arc.ArcInvocationContext; public class InterceptorBindings { + @SuppressWarnings("unchecked") public static Set getInterceptorBindings(InvocationContext invocationContext) { - if (invocationContext instanceof InvocationContextImpl) { - return ((InvocationContextImpl) invocationContext).getInterceptorBindings(); - } else if (invocationContext instanceof AroundInvokeInvocationContext) { - return ((AroundInvokeInvocationContext) invocationContext).getInterceptorBindings(); - } - return null; + return (Set) invocationContext.getContextData().get(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS); } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java index 7a75c1d666205..1c955f961ed71 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanGenerator.java @@ -960,19 +960,20 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator Types.getPackageName(beanCreator.getClassName()))); } - // InvocationContextImpl.aroundConstruct(constructor,aroundConstructs,forward).proceed() ResultHandle invocationContextHandle = create.invokeStaticMethod( - MethodDescriptors.INVOCATION_CONTEXT_AROUND_CONSTRUCT, constructorHandle, + MethodDescriptors.INVOCATION_CONTEXTS_AROUND_CONSTRUCT, constructorHandle, aroundConstructsHandle, func.getInstance(), bindingsHandle); TryBlock tryCatch = create.tryBlock(); CatchBlockCreator exceptionCatch = tryCatch.addCatch(Exception.class); // throw new RuntimeException(e) exceptionCatch.throwException(RuntimeException.class, "Error invoking aroundConstructs", exceptionCatch.getCaughtException()); - tryCatch.assign(instanceHandle, - tryCatch.invokeInterfaceMethod( - MethodDescriptor.ofMethod(InvocationContext.class, "proceed", Object.class), - invocationContextHandle)); + // InvocationContextImpl.aroundConstruct(constructor,aroundConstructs,forward).proceed() + tryCatch.invokeInterfaceMethod(MethodDescriptors.INVOCATION_CONTEXT_PROCEED, + invocationContextHandle); + // instance = InvocationContext.getTarget() + tryCatch.assign(instanceHandle, tryCatch.invokeInterfaceMethod(MethodDescriptors.INVOCATION_CONTEXT_GET_TARGET, + invocationContextHandle)); } else { create.assign(instanceHandle, newInstanceHandle(bean, beanCreator, create, create, providerTypeName, baseName, @@ -1079,7 +1080,7 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator // InvocationContextImpl.postConstruct(instance,postConstructs).proceed() ResultHandle invocationContextHandle = create.invokeStaticMethod( - MethodDescriptors.INVOCATION_CONTEXT_POST_CONSTRUCT, instanceHandle, + MethodDescriptors.INVOCATION_CONTEXTS_POST_CONSTRUCT, instanceHandle, postConstructsHandle, bindingsHandle); TryBlock tryCatch = create.tryBlock(); 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 e504b57263847..e459187f02138 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 @@ -1,9 +1,7 @@ package io.quarkus.arc.processor; -import io.quarkus.arc.AbstractInvocationContext; import io.quarkus.arc.Arc; import io.quarkus.arc.ArcContainer; -import io.quarkus.arc.AroundInvokeInvocationContext; import io.quarkus.arc.ClientProxy; import io.quarkus.arc.CreationalContextImpl; import io.quarkus.arc.FixedValueSupplier; @@ -11,10 +9,11 @@ import io.quarkus.arc.InjectableContext; import io.quarkus.arc.InjectableInterceptor; import io.quarkus.arc.InjectableReferenceProvider; -import io.quarkus.arc.InvocationContextImpl; -import io.quarkus.arc.InvocationContextImpl.InterceptorInvocation; +import io.quarkus.arc.InterceptorInvocation; +import io.quarkus.arc.InvocationContexts; import io.quarkus.arc.MapValueSupplier; import io.quarkus.arc.Reflections; +import io.quarkus.arc.SubclassMethodMetadata; import io.quarkus.gizmo.MethodDescriptor; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -80,6 +79,10 @@ final class MethodDescriptors { "postConstruct", InterceptorInvocation.class, InjectableInterceptor.class, Object.class); + 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( InterceptorInvocation.class, "aroundConstruct", InterceptorInvocation.class, InjectableInterceptor.class, Object.class); @@ -131,26 +134,33 @@ final class MethodDescriptors { static final MethodDescriptor EVENT_CONTEXT_GET_METADATA = MethodDescriptor.ofMethod(EventContext.class, "getMetadata", EventMetadata.class); - static final MethodDescriptor INVOCATION_CONTEXT_AROUND_INVOKE = MethodDescriptor.ofMethod( - AroundInvokeInvocationContext.class, - "create", - AbstractInvocationContext.class, Object.class, Method.class, Function.class, Object[].class, List.class, Set.class); + 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_CONTEXT_AROUND_CONSTRUCT = MethodDescriptor.ofMethod(InvocationContextImpl.class, + static final MethodDescriptor INVOCATION_CONTEXTS_AROUND_CONSTRUCT = MethodDescriptor.ofMethod( + InvocationContexts.class, "aroundConstruct", - InvocationContextImpl.class, Constructor.class, List.class, Supplier.class, Set.class); + InvocationContext.class, Constructor.class, List.class, Supplier.class, Set.class); - static final MethodDescriptor INVOCATION_CONTEXT_POST_CONSTRUCT = MethodDescriptor.ofMethod(InvocationContextImpl.class, + static final MethodDescriptor INVOCATION_CONTEXTS_POST_CONSTRUCT = MethodDescriptor.ofMethod( + InvocationContexts.class, "postConstruct", - InvocationContextImpl.class, Object.class, List.class, Set.class); + InvocationContext.class, Object.class, List.class, Set.class); - static final MethodDescriptor INVOCATION_CONTEXT_PRE_DESTROY = MethodDescriptor.ofMethod(InvocationContextImpl.class, + static final MethodDescriptor INVOCATION_CONTEXTS_PRE_DESTROY = MethodDescriptor.ofMethod(InvocationContexts.class, "preDestroy", - InvocationContextImpl.class, Object.class, List.class, Set.class); + InvocationContext.class, Object.class, List.class, Set.class); static final MethodDescriptor INVOCATION_CONTEXT_PROCEED = MethodDescriptor.ofMethod(InvocationContext.class, "proceed", Object.class); + 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, "addDependencyToParent", void.class, InjectableBean.class, Object.class, CreationalContext.class); @@ -172,6 +182,10 @@ final class MethodDescriptors { static final MethodDescriptor GET_IDENTIFIER = MethodDescriptor.ofMethod(InjectableBean.class, "getIdentifier", String.class); + static final MethodDescriptor SUBCLASS_METHOD_METADATA_CONSTRUCTOR = MethodDescriptor.ofConstructor( + SubclassMethodMetadata.class, + List.class, Method.class, Set.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 dfe3cdb0d79ed..e75d372e611e6 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 @@ -6,8 +6,8 @@ import io.quarkus.arc.ArcUndeclaredThrowableException; import io.quarkus.arc.InjectableInterceptor; -import io.quarkus.arc.InvocationContextImpl.InterceptorInvocation; import io.quarkus.arc.Subclass; +import io.quarkus.arc.SubclassMethodMetadata; import io.quarkus.arc.processor.BeanInfo.InterceptionInfo; import io.quarkus.arc.processor.ResourceOutput.Resource; import io.quarkus.gizmo.BytecodeCreator; @@ -22,6 +22,7 @@ import io.quarkus.gizmo.MethodDescriptor; import io.quarkus.gizmo.ResultHandle; import io.quarkus.gizmo.TryBlock; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; @@ -32,6 +33,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Optional; +import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -55,9 +57,17 @@ public class SubclassGenerator extends AbstractGenerator { private static final DotName JAVA_LANG_RUNTIME_EXCEPTION = DotNames.create(RuntimeException.class.getName()); static final String SUBCLASS_SUFFIX = "_Subclass"; - static final String DESTROY_METHOD_NAME = "arc$destroy"; + 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", + Method.class); + protected static final FieldDescriptor FIELD_METADATA_CHAIN = FieldDescriptor.of(SubclassMethodMetadata.class, "chain", + List.class); + protected static final FieldDescriptor FIELD_METADATA_BINDINGS = FieldDescriptor.of(SubclassMethodMetadata.class, + "bindings", Set.class); + private final Predicate applicationClassPredicate; static String generatedName(DotName providerTypeName, String baseName) { @@ -109,8 +119,8 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be String providerTypeName, ReflectionRegistration reflectionRegistration) { + // Constructor parameters List parameterTypes = new ArrayList<>(); - // First constructor injection points Optional constructorInjection = bean.getConstructorInjection(); if (constructorInjection.isPresent()) { @@ -119,16 +129,13 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be } } int superParamsSize = parameterTypes.size(); - // CreationalContext parameterTypes.add(CreationalContext.class.getName()); - // Interceptor providers List boundInterceptors = bean.getBoundInterceptors(); for (int j = 0; j < boundInterceptors.size(); j++) { parameterTypes.add(InjectableInterceptor.class.getName()); } - MethodCreator constructor = subclass.getMethodCreator(Methods.INIT, "V", parameterTypes.toArray(new String[0])); ResultHandle creationalContextHandle = constructor.getMethodParam(superParamsSize); @@ -142,7 +149,7 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be parameterTypes.subList(0, superParamsSize).toArray(new String[0])), constructor.getThis(), superParams); - // we build a map for each interceptor instance created, so that they are shared + // We build a map for each interceptor instance created, so that they can be shared // Map where InjectableInterceptor.getIdentifier() is key and Object is instance of the interceptor for this bean ResultHandle interceptorInstanceMap = constructor.newInstance(MethodDescriptor.ofConstructor(HashMap.class)); // build a map that links InterceptorInfo to ResultHandle @@ -170,7 +177,8 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be InterceptionInfo preDestroys = bean.getLifecycleInterceptors(InterceptionType.PRE_DESTROY); if (!preDestroys.isEmpty()) { // private final List preDestroys - preDestroysField = subclass.getFieldCreator("preDestroys", DescriptorUtils.extToInt(ArrayList.class.getName())) + preDestroysField = subclass + .getFieldCreator(FIELD_NAME_PREDESTROYS, DescriptorUtils.extToInt(ArrayList.class.getName())) .setModifiers(ACC_PRIVATE | ACC_FINAL); // preDestroys = new ArrayList<>() constructor.writeInstanceField(preDestroysField.getFieldDescriptor(), constructor.getThis(), @@ -181,8 +189,7 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be interceptorInstanceMap, constructor.invokeInterfaceMethod(MethodDescriptors.GET_IDENTIFIER, interceptorToResultHandle.get(interceptor))); ResultHandle interceptionInvocation = constructor.invokeStaticMethod( - MethodDescriptor.ofMethod(InterceptorInvocation.class, "preDestroy", - InterceptorInvocation.class, InjectableInterceptor.class, Object.class), + MethodDescriptors.INTERCEPTOR_INVOCATION_PRE_DESTROY, interceptorToResultHandle.get(interceptor), interceptorInstance); constructor.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, @@ -192,20 +199,13 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be } // Init intercepted methods and interceptor chains - // private final Map> interceptorChains - FieldCreator interceptorChainsField = subclass.getFieldCreator("interceptorChains", Map.class.getName()) + // private final Map metadata + // metadata = new HashMap<>() + FieldCreator metadataField = subclass.getFieldCreator(FIELD_NAME_METADATA, Map.class.getName()) .setModifiers(ACC_PRIVATE | ACC_FINAL); - // interceptorChains = new HashMap<>() - constructor.writeInstanceField(interceptorChainsField.getFieldDescriptor(), constructor.getThis(), - constructor.newInstance(MethodDescriptor.ofConstructor(HashMap.class))); - ResultHandle interceptorChainsHandle = constructor.readInstanceField(interceptorChainsField.getFieldDescriptor(), - constructor.getThis()); - // private final Map methods - FieldCreator methodsField = subclass.getFieldCreator("methods", DescriptorUtils.extToInt(Map.class.getName())) - .setModifiers(ACC_PRIVATE | ACC_FINAL); - constructor.writeInstanceField(methodsField.getFieldDescriptor(), constructor.getThis(), - constructor.newInstance(MethodDescriptor.ofConstructor(HashMap.class))); - ResultHandle methodsHandle = constructor.readInstanceField(methodsField.getFieldDescriptor(), constructor.getThis()); + ResultHandle metadataHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashMap.class)); + constructor.writeInstanceField(metadataField.getFieldDescriptor(), constructor.getThis(), + metadataHandle); int methodIdx = 1; for (Entry entry : bean.getInterceptedMethods().entrySet()) { @@ -227,9 +227,8 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be interceptorToResultHandle.get(interceptor), interceptorInstance); constructor.invokeInterfaceMethod(MethodDescriptors.LIST_ADD, chainHandle, interceptionInvocation); } - // interceptorChains.put("m1", m1Chain) - constructor.invokeInterfaceMethod(MethodDescriptors.MAP_PUT, interceptorChainsHandle, methodIdHandle, chainHandle); - // methods.put("m1", Reflections.findMethod(org.jboss.weld.arc.test.interceptors.SimpleBean.class,"foo",java.lang.String.class)) + + // Method method = Reflections.findMethod(org.jboss.weld.arc.test.interceptors.SimpleBean.class,"foo",java.lang.String.class) ResultHandle[] paramsHandles = new ResultHandle[3]; paramsHandles[0] = constructor.loadClass(providerTypeName); paramsHandles[1] = constructor.load(method.name()); @@ -245,15 +244,28 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be } ResultHandle methodHandle = constructor.invokeStaticMethod(MethodDescriptors.REFLECTIONS_FIND_METHOD, paramsHandles); - constructor.invokeInterfaceMethod(MethodDescriptors.MAP_PUT, methodsHandle, methodIdHandle, methodHandle); + + ResultHandle bindingsHandle = constructor.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); + for (AnnotationInstance binding : interceptedMethod.bindings) { + // Create annotation literals first + ClassInfo bindingClass = bean.getDeployment().getInterceptorBinding(binding.name()); + constructor.invokeInterfaceMethod(MethodDescriptors.SET_ADD, bindingsHandle, + annotationLiterals.process(constructor, classOutput, bindingClass, binding, + Types.getPackageName(subclass.getClassName()))); + } + + ResultHandle methodMetadataHandle = constructor.newInstance(MethodDescriptors.SUBCLASS_METHOD_METADATA_CONSTRUCTOR, + chainHandle, methodHandle, bindingsHandle); + // metadata.put("m1", new SubclassMethodMetadata(...)) + constructor.invokeInterfaceMethod(MethodDescriptors.MAP_PUT, metadataHandle, methodIdHandle, methodMetadataHandle); // Needed when running on substrate VM reflectionRegistration.registerMethod(method); // Finally create the forwarding method createForwardingMethod(classOutput, bean, method, methodId, subclass, providerTypeName, - interceptorChainsField.getFieldDescriptor(), - methodsField.getFieldDescriptor(), interceptedMethod); + metadataField.getFieldDescriptor(), + interceptedMethod); } constructor.returnValue(null); @@ -262,7 +274,7 @@ protected FieldDescriptor createConstructor(ClassOutput classOutput, BeanInfo be private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, MethodInfo method, String methodId, ClassCreator subclass, - String providerTypeName, FieldDescriptor interceptorChainsField, FieldDescriptor methodsField, + String providerTypeName, FieldDescriptor metadataField, InterceptionInfo interceptedMethod) { MethodDescriptor originalMethodDescriptor = MethodDescriptor.of(method); @@ -277,7 +289,7 @@ private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, Meth // if(!this.bean == null) return super.foo() BytecodeCreator notConstructed = forwardMethod - .ifNull(forwardMethod.readInstanceField(methodsField, forwardMethod.getThis())).trueBranch(); + .ifNull(forwardMethod.readInstanceField(metadataField, forwardMethod.getThis())).trueBranch(); ResultHandle[] params = new ResultHandle[method.parameters().size()]; for (int i = 0; i < method.parameters().size(); ++i) { params[i] = notConstructed.getMethodParam(i); @@ -315,8 +327,6 @@ private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, Meth forwardMethod.addException(declaredException.name().toString()); } - // InvocationContext - // (java.lang.String) InvocationContextImpl.aroundInvoke(this, methods.get("m1"), params, interceptorChains.get("m1"), forward).proceed() TryBlock tryCatch = forwardMethod.tryBlock(); // catch exceptions declared on the original method boolean addCatchRuntimeException = true; @@ -346,27 +356,15 @@ private void createForwardingMethod(ClassOutput classOutput, BeanInfo bean, Meth catchOtherExceptions.throwException(ArcUndeclaredThrowableException.class, "Error invoking subclass method", catchOtherExceptions.getCaughtException()); } - // InvocationContextImpl.aroundInvoke(this, methods.get("m1"), params, interceptorChains.get("m1"), forward) + // InvocationContexts.performAroundInvoke(...) ResultHandle methodIdHandle = tryCatch.load(methodId); - ResultHandle interceptedMethodHandle = tryCatch.invokeInterfaceMethod(MethodDescriptors.MAP_GET, - tryCatch.readInstanceField(methodsField, tryCatch.getThis()), methodIdHandle); - ResultHandle interceptedChainHandle = tryCatch.invokeInterfaceMethod(MethodDescriptors.MAP_GET, - tryCatch.readInstanceField(interceptorChainsField, tryCatch.getThis()), methodIdHandle); - // Interceptor bindings - ResultHandle bindingsHandle = tryCatch.newInstance(MethodDescriptor.ofConstructor(HashSet.class)); - for (AnnotationInstance binding : interceptedMethod.bindings) { - // Create annotation literals first - ClassInfo bindingClass = bean.getDeployment().getInterceptorBinding(binding.name()); - tryCatch.invokeInterfaceMethod(MethodDescriptors.SET_ADD, bindingsHandle, - annotationLiterals.process(tryCatch, classOutput, bindingClass, binding, - Types.getPackageName(subclass.getClassName()))); - } - - ResultHandle invocationContext = tryCatch.invokeStaticMethod(MethodDescriptors.INVOCATION_CONTEXT_AROUND_INVOKE, + ResultHandle methodMetadataHandle = tryCatch.invokeInterfaceMethod(MethodDescriptors.MAP_GET, + tryCatch.readInstanceField(metadataField, tryCatch.getThis()), methodIdHandle); + ResultHandle ret = tryCatch.invokeStaticMethod(MethodDescriptors.INVOCATION_CONTEXTS_PERFORM_AROUND_INVOKE, tryCatch.getThis(), - interceptedMethodHandle, func.getInstance(), paramsHandle, interceptedChainHandle, bindingsHandle); - // InvocationContext.proceed() - ResultHandle ret = tryCatch.invokeInterfaceMethod(MethodDescriptors.INVOCATION_CONTEXT_PROCEED, invocationContext); + tryCatch.readInstanceField(FIELD_METADATA_METHOD, methodMetadataHandle), func.getInstance(), paramsHandle, + tryCatch.readInstanceField(FIELD_METADATA_CHAIN, methodMetadataHandle), + tryCatch.readInstanceField(FIELD_METADATA_BINDINGS, methodMetadataHandle)); tryCatch.returnValue(superResult != null ? ret : null); } @@ -402,7 +400,7 @@ protected void createDestroy(ClassOutput classOutput, BeanInfo bean, ClassCreato exception.throwException(RuntimeException.class, "Error destroying subclass", exception.getCaughtException()); // InvocationContextImpl.preDestroy(this,predestroys) - ResultHandle invocationContext = tryCatch.invokeStaticMethod(MethodDescriptors.INVOCATION_CONTEXT_PRE_DESTROY, + ResultHandle invocationContext = tryCatch.invokeStaticMethod(MethodDescriptors.INVOCATION_CONTEXTS_PRE_DESTROY, tryCatch.getThis(), predestroysHandle, bindingsHandle); diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AbstractInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AbstractInvocationContext.java index f73fce7905b93..b43b12c4f53fd 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AbstractInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AbstractInvocationContext.java @@ -3,64 +3,48 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Method; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; -import java.util.function.Function; -import javax.interceptor.InvocationContext; +import java.util.function.Supplier; -public abstract class AbstractInvocationContext implements InvocationContext { +abstract class AbstractInvocationContext implements ArcInvocationContext, Supplier> { - protected Map contextData; protected final Method method; - protected Object[] parameters; - protected final Object target; - protected final Object timer; protected final Constructor constructor; protected final Set interceptorBindings; - protected Function aroundInvokeForward; - - protected AbstractInvocationContext(Object target, Method method, Function aroundInvokeForward, - Object[] parameters, - Map contextData, Set interceptorBindings) { - this(target, method, aroundInvokeForward, null, parameters, null, contextData, interceptorBindings); - } + protected final List chain; + protected Object target; + protected Object[] parameters; + // The map is initialized lazily but we need to use a holder so that all interceptors in the chain can access the same data + protected LazyValue> contextData; - protected AbstractInvocationContext(Object target, Method method, Function aroundInvokeForward, + protected AbstractInvocationContext(Object target, Method method, Constructor constructor, - Object[] parameters, Object timer, Map contextData, - Set interceptorBindings) { + Object[] parameters, LazyValue> contextData, + Set interceptorBindings, List chain) { this.target = target; this.method = method; - this.aroundInvokeForward = aroundInvokeForward; this.constructor = constructor; this.parameters = parameters; - this.timer = timer; - this.contextData = contextData; + this.contextData = contextData != null ? contextData : new LazyValue<>(this); this.interceptorBindings = interceptorBindings; + this.chain = chain; } @Override public Map getContextData() { - if (contextData == null) { - contextData = newContextData(interceptorBindings); - } - return contextData; + return contextData.get(); } + @Override public Set getInterceptorBindings() { return interceptorBindings; } - @Override - public abstract Object proceed() throws Exception; - - protected static Map newContextData(Set interceptorBindings) { - Map result = new HashMap(); - result.put(InvocationContextImpl.KEY_INTERCEPTOR_BINDINGS, interceptorBindings); - return result; - } - @Override public Method getMethod() { return method; @@ -68,20 +52,37 @@ public Method getMethod() { @Override public Object[] getParameters() { - if (parameters == null) { - throw new IllegalStateException(); - } return parameters; } @Override - public void setParameters(Object[] params) throws IllegalStateException, IllegalArgumentException { - if (parameters == null) { - throw new IllegalStateException(); - } + public void setParameters(Object[] params) { + validateParameters(params); this.parameters = params; } + protected void validateParameters(Object[] params) { + int newParametersCount = Objects.requireNonNull(params).length; + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length != newParametersCount) { + throw new IllegalArgumentException( + "Wrong number of parameters - method has " + Arrays.toString(parameterTypes) + ", attempting to set " + + Arrays.toString(params)); + } + for (int i = 0; i < params.length; i++) { + if (parameterTypes[i].isPrimitive() && params[i] == null) { + throw new IllegalArgumentException("Trying to set a null value to a primitive parameter [position: " + i + + ", type: " + parameterTypes[i] + "]"); + } + if (params[i] != null) { + if (!params[i].getClass().equals(parameterTypes[i])) { + throw new IllegalArgumentException("The parameter type [" + params[i].getClass() + + "] does not match the type for the target method [" + parameterTypes[i] + "]"); + } + } + } + } + @Override public Object getTarget() { return target; @@ -89,7 +90,7 @@ public Object getTarget() { @Override public Object getTimer() { - return timer; + return null; } @Override @@ -97,10 +98,11 @@ public Constructor getConstructor() { return constructor; } - protected Function getAroundInvokeForward() { - return aroundInvokeForward; + @Override + public Map get() { + Map result = new HashMap(); + result.put(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS, interceptorBindings); + return result; } - abstract Object proceedInternal() throws Exception; - } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcInvocationContext.java new file mode 100644 index 0000000000000..b365f7c517857 --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/ArcInvocationContext.java @@ -0,0 +1,23 @@ +package io.quarkus.arc; + +import java.lang.annotation.Annotation; +import java.util.Set; +import javax.interceptor.InvocationContext; + +/** + * + */ +public interface ArcInvocationContext extends InvocationContext { + + /** + * This key can be used to obtain the interceptor bindings from the context data. + */ + String KEY_INTERCEPTOR_BINDINGS = "io.quarkus.arc.interceptorBindings"; + + /** + * + * @return the interceptor bindings + */ + Set getInterceptorBindings(); + +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AroundConstructInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AroundConstructInvocationContext.java new file mode 100644 index 0000000000000..bcbf14874d469 --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AroundConstructInvocationContext.java @@ -0,0 +1,26 @@ +package io.quarkus.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.util.List; +import java.util.Set; +import java.util.function.Supplier; + +/** + * An InvocationContext implementation used for AroundConstruct callbacks. + */ +class AroundConstructInvocationContext extends LifecycleCallbackInvocationContext { + + private final Supplier aroundConstructForward; + + AroundConstructInvocationContext(Constructor constructor, Set interceptorBindings, + List chain, Supplier aroundConstructForward) { + super(null, constructor, interceptorBindings, chain); + this.aroundConstructForward = aroundConstructForward; + } + + protected void interceptorChainCompleted() throws Exception { + target = aroundConstructForward.get(); + } + +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AroundInvokeInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AroundInvokeInvocationContext.java index 491290fda6ee5..1b2a08b668459 100644 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AroundInvokeInvocationContext.java +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/AroundInvokeInvocationContext.java @@ -1,7 +1,7 @@ package io.quarkus.arc; import java.lang.annotation.Annotation; -import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.List; import java.util.Map; @@ -9,38 +9,60 @@ import java.util.function.Function; import javax.interceptor.InvocationContext; -public abstract class AroundInvokeInvocationContext extends AbstractInvocationContext { +/** + * Special type of InvocationContext for AroundInvoke interceptors. + *

+ * A new instance of {@link AroundInvokeInvocationContext} is created for each interceptor in the chain. This does not comply + * with the spec but allows for "asynchronous continuation" of an interceptor chain execution. In other words, it is possible to + * "cut off" the chain (interceptors executed before dispatch return immediately) and execute all remaining interceptors + * asynchronously, possibly on a different thread. + *

+ * Note that context data and method parameters are mutable and are not guarded/synchronized. We expect them to be modified + * before or after dispatch. If modified before and after dispatch an unpredicatble behavior may occur. + */ +class AroundInvokeInvocationContext extends AbstractInvocationContext { - protected AroundInvokeInvocationContext(Object target, Method method, - Function aroundInvokeForward, Object[] parameters, - Map contextData, Set interceptorBindings) { - super(target, method, aroundInvokeForward, parameters, contextData, interceptorBindings); - } + private final int position; + private final Function aroundInvokeForward; - protected AroundInvokeInvocationContext(Object target, Method method, - Function aroundInvokeForward, Constructor constructor, - Object[] parameters, Object timer, Map contextData, Set interceptorBindings) { - super(target, method, aroundInvokeForward, constructor, parameters, timer, contextData, interceptorBindings); + AroundInvokeInvocationContext(Object target, Method method, Object[] parameters, + LazyValue> contextData, Set interceptorBindings, int position, + List chain, Function aroundInvokeForward) { + super(target, method, null, parameters, contextData, interceptorBindings, chain); + this.position = position; + this.aroundInvokeForward = aroundInvokeForward; } - public static AbstractInvocationContext create(Object instance, Method method, - Function aroundInvokeForward, Object[] args, - List chain, - Set interceptorBindings) { - if (chain.size() == 0) { - // terminal invocation context, known to be last context in chain; invoked the original method directly - return new TerminalInvocationContext(instance, method, aroundInvokeForward, args, null, interceptorBindings); - } else { - // non-terminal invocation context, meaning there are more interceptors to be invoked - return new NonTerminalInvocationContext(instance, method, aroundInvokeForward, args, interceptorBindings, chain); - } + static Object perform(Object target, Method method, + Function aroundInvokeForward, Object[] parameters, + List chain, + Set interceptorBindings) throws Exception { + + return chain.get(0).invoke(new AroundInvokeInvocationContext(target, method, + parameters, null, interceptorBindings, 1, chain, aroundInvokeForward)); } @Override public Object proceed() throws Exception { - return proceedInternal(); + try { + if (position < chain.size()) { + // Invoke the next interceptor in the chain + return chain.get(position).invoke(new AroundInvokeInvocationContext(target, method, + parameters, contextData, interceptorBindings, position + 1, chain, aroundInvokeForward)); + } else { + // Invoke the target method + return aroundInvokeForward.apply(this); + } + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof Error) { + throw (Error) cause; + } + if (cause instanceof Exception) { + throw (Exception) cause; + } + throw new RuntimeException(cause); + } } - @Override - abstract Object proceedInternal() throws Exception; } diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InterceptorInvocation.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InterceptorInvocation.java new file mode 100644 index 0000000000000..fdecb0b827bbe --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InterceptorInvocation.java @@ -0,0 +1,42 @@ +package io.quarkus.arc; + +import javax.enterprise.inject.spi.InterceptionType; +import javax.interceptor.InvocationContext; + +public final class InterceptorInvocation { + + public static InterceptorInvocation aroundInvoke(InjectableInterceptor interceptor, Object interceptorInstance) { + return new InterceptorInvocation(InterceptionType.AROUND_INVOKE, interceptor, interceptorInstance); + } + + public static InterceptorInvocation postConstruct(InjectableInterceptor interceptor, Object interceptorInstance) { + return new InterceptorInvocation(InterceptionType.POST_CONSTRUCT, interceptor, interceptorInstance); + } + + public static InterceptorInvocation preDestroy(InjectableInterceptor interceptor, Object interceptorInstance) { + return new InterceptorInvocation(InterceptionType.PRE_DESTROY, interceptor, interceptorInstance); + } + + public static InterceptorInvocation aroundConstruct(InjectableInterceptor interceptor, Object interceptorInstance) { + return new InterceptorInvocation(InterceptionType.AROUND_CONSTRUCT, interceptor, interceptorInstance); + } + + private final InterceptionType interceptionType; + + @SuppressWarnings("rawtypes") + private final InjectableInterceptor interceptor; + + private final Object interceptorInstance; + + InterceptorInvocation(InterceptionType interceptionType, InjectableInterceptor interceptor, + Object interceptorInstance) { + this.interceptionType = interceptionType; + this.interceptor = interceptor; + this.interceptorInstance = interceptorInstance; + } + + @SuppressWarnings("unchecked") + Object invoke(InvocationContext ctx) throws Exception { + return interceptor.intercept(interceptionType, interceptorInstance, ctx); + } +} \ No newline at end of file diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InvocationContextImpl.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InvocationContextImpl.java deleted file mode 100644 index e78c5a2d11bba..0000000000000 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InvocationContextImpl.java +++ /dev/null @@ -1,244 +0,0 @@ -package io.quarkus.arc; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import java.util.function.Supplier; -import javax.enterprise.inject.spi.InterceptionType; -import javax.interceptor.InvocationContext; - -/** - * - * @author Martin Kouba - */ -public class InvocationContextImpl implements InvocationContext { - - public static final String KEY_INTERCEPTOR_BINDINGS = "io.quarkus.arc.interceptorBindings"; - - // Around invoke is done via io.quarkus.arc.AroundInvokeInvocationContext#create() - - /** - * - * @param target - * @param chain - * @param interceptorBindings - * @return a new {@link javax.annotation.PostConstruct} invocation context - */ - public static InvocationContextImpl postConstruct(Object target, List chain, - Set interceptorBindings) { - return new InvocationContextImpl(target, null, null, null, chain, null, null, interceptorBindings); - } - - /** - * - * @param target - * @param chain - * @param interceptorBindings - * @return a new {@link javax.annotation.PreDestroy} invocation context - */ - public static InvocationContextImpl preDestroy(Object target, List chain, - Set interceptorBindings) { - return new InvocationContextImpl(target, null, null, null, chain, null, null, interceptorBindings); - } - - /** - * - * @param target - * @param chain - * @param interceptorBindings - * @return a new {@link javax.interceptor.AroundConstruct} invocation context - */ - public static InvocationContextImpl aroundConstruct(Constructor constructor, List chain, - Supplier aroundConstructForward, - Set interceptorBindings) { - return new InvocationContextImpl(null, null, constructor, null, chain, null, aroundConstructForward, - interceptorBindings); - } - - private final AtomicReference target; - - private final Method method; - - private final Constructor constructor; - - private Object[] args; - - private int position; - - private final Map contextData; - - private final List chain; - - private final Function aroundInvokeForward; - - private final Supplier aroundConstructForward; - - private final Set interceptorBindings; - - /** - * @param target - * @param method - * @param constructor - * @param args - * @param chain - * @param aroundInvokeForward - * @param aroundConstructForward - * @param interceptorBindings - */ - InvocationContextImpl(Object target, Method method, Constructor constructor, Object[] args, - List chain, - Function aroundInvokeForward, Supplier aroundConstructForward, - Set interceptorBindings) { - this.target = new AtomicReference<>(target); - this.method = method; - this.constructor = constructor; - this.args = args; - this.position = 0; - this.chain = chain; - this.aroundInvokeForward = aroundInvokeForward; - this.aroundConstructForward = aroundConstructForward; - this.interceptorBindings = interceptorBindings; - contextData = new HashMap<>(); - contextData.put(KEY_INTERCEPTOR_BINDINGS, interceptorBindings); - } - - boolean hasNextInterceptor() { - return position < chain.size(); - } - - protected Object invokeNext() throws Exception { - int oldPosition = position; - try { - return chain.get(position++).invoke(this); - } finally { - position = oldPosition; - } - } - - protected Object interceptorChainCompleted() throws Exception { - if (aroundInvokeForward != null) { - return aroundInvokeForward.apply(this); - } - if (aroundConstructForward != null) { - target.set(aroundConstructForward.get()); - } - return null; - } - - @Override - public Object proceed() throws Exception { - try { - if (hasNextInterceptor()) { - if (aroundConstructForward != null) { - invokeNext(); - return target.get(); - } else { - return invokeNext(); - } - } else { - // The last interceptor in chain called proceed() - return interceptorChainCompleted(); - } - } catch (InvocationTargetException e) { - Throwable cause = e.getCause(); - if (cause instanceof Error) { - throw (Error) cause; - } - if (cause instanceof Exception) { - throw (Exception) cause; - } - throw new RuntimeException(cause); - } - } - - @Override - public Object getTarget() { - return target.get(); - } - - @Override - public Method getMethod() { - return method; - } - - @Override - public Constructor getConstructor() { - return constructor; - } - - @Override - public Object[] getParameters() throws IllegalStateException { - if (args == null) { - throw new IllegalStateException(); - } - return args; - } - - @Override - public void setParameters(Object[] params) throws IllegalStateException, IllegalArgumentException { - if (args == null) { - throw new IllegalStateException(); - } - this.args = params; - } - - @Override - public Map getContextData() { - return contextData; - } - - @Override - public Object getTimer() { - return null; - } - - public Set getInterceptorBindings() { - return interceptorBindings; - } - - public static class InterceptorInvocation { - - public static InterceptorInvocation aroundInvoke(InjectableInterceptor interceptor, Object interceptorInstance) { - return new InterceptorInvocation(InterceptionType.AROUND_INVOKE, interceptor, interceptorInstance); - } - - public static InterceptorInvocation postConstruct(InjectableInterceptor interceptor, Object interceptorInstance) { - return new InterceptorInvocation(InterceptionType.POST_CONSTRUCT, interceptor, interceptorInstance); - } - - public static InterceptorInvocation preDestroy(InjectableInterceptor interceptor, Object interceptorInstance) { - return new InterceptorInvocation(InterceptionType.PRE_DESTROY, interceptor, interceptorInstance); - } - - public static InterceptorInvocation aroundConstruct(InjectableInterceptor interceptor, Object interceptorInstance) { - return new InterceptorInvocation(InterceptionType.AROUND_CONSTRUCT, interceptor, interceptorInstance); - } - - private final InterceptionType interceptionType; - - @SuppressWarnings("rawtypes") - private final InjectableInterceptor interceptor; - - private final Object interceptorInstance; - - InterceptorInvocation(InterceptionType interceptionType, InjectableInterceptor interceptor, - Object interceptorInstance) { - this.interceptionType = interceptionType; - this.interceptor = interceptor; - this.interceptorInstance = interceptorInstance; - } - - @SuppressWarnings("unchecked") - Object invoke(InvocationContext ctx) throws Exception { - return interceptor.intercept(interceptionType, interceptorInstance, ctx); - } - } - -} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InvocationContexts.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InvocationContexts.java new file mode 100644 index 0000000000000..2e22e64a1f9f0 --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/InvocationContexts.java @@ -0,0 +1,70 @@ +package io.quarkus.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; +import javax.interceptor.InvocationContext; + +public final class InvocationContexts { + + /** + * + * @param target + * @param method + * @param aroundInvokeForward + * @param args + * @param chain + * @param interceptorBindings + * @return the return value + * @throws Exception + */ + public static Object performAroundInvoke(Object target, Method method, + Function aroundInvokeForward, Object[] args, + List chain, + Set interceptorBindings) throws Exception { + return AroundInvokeInvocationContext.perform(target, method, aroundInvokeForward, args, chain, interceptorBindings); + } + + /** + * + * @param target + * @param chain + * @param interceptorBindings + * @return a new invocation context + */ + public static InvocationContext postConstruct(Object target, List chain, + Set interceptorBindings) { + return new LifecycleCallbackInvocationContext(target, null, interceptorBindings, chain); + } + + /** + * + * @param target + * @param chain + * @param interceptorBindings + * @return a new invocation context + */ + public static InvocationContext preDestroy(Object target, List chain, + Set interceptorBindings) { + return new LifecycleCallbackInvocationContext(target, null, interceptorBindings, chain); + } + + /** + * + * @param target + * @param chain + * @param interceptorBindings + * @return a new {@link javax.interceptor.AroundConstruct} invocation context + */ + public static InvocationContext aroundConstruct(Constructor constructor, + List chain, + Supplier aroundConstructForward, + Set interceptorBindings) { + return new AroundConstructInvocationContext(constructor, interceptorBindings, chain, aroundConstructForward); + } + +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/LifecycleCallbackInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/LifecycleCallbackInvocationContext.java new file mode 100644 index 0000000000000..bd8508ba56e98 --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/LifecycleCallbackInvocationContext.java @@ -0,0 +1,59 @@ +package io.quarkus.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.Set; + +/** + * A simple InvocationContext implementation used for PostConstruct and PreDestroy callbacks. + *

+ * All lifecycle callback interceptors of a specific chain must be invoked on the same thread. + */ +class LifecycleCallbackInvocationContext extends AbstractInvocationContext { + + private int position = 0; + + LifecycleCallbackInvocationContext(Object target, Constructor constructor, Set interceptorBindings, + List chain) { + super(target, null, constructor, null, null, interceptorBindings, chain); + } + + @Override + public Object proceed() throws Exception { + try { + if (position < chain.size()) { + // Invoke the next interceptor in the chain + invokeNext(); + } else { + // The invocation of proceed in the last interceptor method in the chain + interceptorChainCompleted(); + } + // The return value of a lifecycle callback is ignored + return null; + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof Error) { + throw (Error) cause; + } + if (cause instanceof Exception) { + throw (Exception) cause; + } + throw new RuntimeException(cause); + } + } + + protected void interceptorChainCompleted() throws Exception { + // No-op + } + + protected Object invokeNext() throws Exception { + try { + return chain.get(position++).invoke(this); + } finally { + position--; + } + } + +} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/NonTerminalInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/NonTerminalInvocationContext.java deleted file mode 100644 index df5bffa6d2f6d..0000000000000 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/NonTerminalInvocationContext.java +++ /dev/null @@ -1,52 +0,0 @@ -package io.quarkus.arc; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import javax.interceptor.InvocationContext; - -public class NonTerminalInvocationContext extends AroundInvokeInvocationContext { - - private final int position; - private final List chain; - - public NonTerminalInvocationContext(Object target, Method method, Function aroundInvokeForward, - Object[] parameters, - Set interceptorBindings, - List chain) { - this(target, method, aroundInvokeForward, parameters, newContextData(interceptorBindings), interceptorBindings, 0, - chain); - } - - public NonTerminalInvocationContext(NonTerminalInvocationContext ctx) { - this(ctx.getTarget(), ctx.getMethod(), ctx.getAroundInvokeForward(), ctx.getParameters(), ctx.contextData, - ctx.getInterceptorBindings(), ctx.position + 1, - ctx.chain); - } - - private NonTerminalInvocationContext(Object target, Method method, Function aroundInvokeForward, - Object[] parameters, - Map contextData, - Set interceptorBindings, int position, List chain) { - super(target, method, aroundInvokeForward, parameters, contextData, interceptorBindings); - this.position = position; - this.chain = chain; - } - - @Override - public Object proceedInternal() throws Exception { - AbstractInvocationContext ctx = createNextContext(); - return chain.get(position).invoke(ctx); - } - - private AbstractInvocationContext createNextContext() { - if (position + 1 == chain.size()) { - return new TerminalInvocationContext(this); - } else { - return new NonTerminalInvocationContext(this); - } - } -} diff --git a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/SubclassMethodMetadata.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/SubclassMethodMetadata.java new file mode 100644 index 0000000000000..d103b5a5a393a --- /dev/null +++ b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/SubclassMethodMetadata.java @@ -0,0 +1,20 @@ +package io.quarkus.arc; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Set; + +public class SubclassMethodMetadata { + + public final List chain; + public final Method method; + public final Set bindings; + + public SubclassMethodMetadata(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/TerminalInvocationContext.java b/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/TerminalInvocationContext.java deleted file mode 100644 index ae12438a1913c..0000000000000 --- a/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/TerminalInvocationContext.java +++ /dev/null @@ -1,32 +0,0 @@ -package io.quarkus.arc; - -import java.lang.annotation.Annotation; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import javax.interceptor.InvocationContext; - -public class TerminalInvocationContext extends AroundInvokeInvocationContext { - - public TerminalInvocationContext(Object target, Method method, Function aroundInvokeForward, - Object[] parameters, - Map contextData, - Set interceptorBindings) { - super(target, method, aroundInvokeForward, parameters, - (contextData == null) ? null : new HashMap(contextData), - interceptorBindings); - } - - public TerminalInvocationContext(NonTerminalInvocationContext ctx) { - super(ctx.getTarget(), ctx.getMethod(), ctx.getAroundInvokeForward(), ctx.getParameters(), ctx.contextData, - ctx.getInterceptorBindings()); - } - - @Override - public Object proceedInternal() throws Exception { - return getAroundInvokeForward().apply(this); - } - -} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LifecycleInterceptor.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LifecycleInterceptor.java index 4e3269b2110a8..911c2c3a34eb1 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LifecycleInterceptor.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/LifecycleInterceptor.java @@ -1,6 +1,6 @@ package io.quarkus.arc.test.interceptors; -import io.quarkus.arc.InvocationContextImpl; +import io.quarkus.arc.ArcInvocationContext; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import javax.annotation.PostConstruct; @@ -21,7 +21,7 @@ public class LifecycleInterceptor { @PostConstruct void simpleInit(InvocationContext ctx) { - Object bindings = ctx.getContextData().get(InvocationContextImpl.KEY_INTERCEPTOR_BINDINGS); + Object bindings = ctx.getContextData().get(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS); if (bindings == null) { throw new IllegalArgumentException("No bindings found"); } @@ -30,7 +30,7 @@ void simpleInit(InvocationContext ctx) { @PreDestroy void simpleDestroy(InvocationContext ctx) { - Object bindings = ctx.getContextData().get(InvocationContextImpl.KEY_INTERCEPTOR_BINDINGS); + Object bindings = ctx.getContextData().get(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS); if (bindings == null) { throw new IllegalArgumentException("No bindings found"); } @@ -39,7 +39,7 @@ void simpleDestroy(InvocationContext ctx) { @AroundConstruct void simpleAroundConstruct(InvocationContext ctx) throws Exception { - Object bindings = ctx.getContextData().get(InvocationContextImpl.KEY_INTERCEPTOR_BINDINGS); + Object bindings = ctx.getContextData().get(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS); if (bindings == null) { throw new IllegalArgumentException("No bindings found"); } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/SimpleInterceptor.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/SimpleInterceptor.java index e2a6b0b5195fd..a58ebbf25bb91 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/SimpleInterceptor.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/SimpleInterceptor.java @@ -1,6 +1,6 @@ package io.quarkus.arc.test.interceptors; -import io.quarkus.arc.InvocationContextImpl; +import io.quarkus.arc.ArcInvocationContext; import javax.annotation.Priority; import javax.inject.Inject; import javax.interceptor.AroundInvoke; @@ -17,7 +17,7 @@ public class SimpleInterceptor { @AroundInvoke Object mySuperCoolAroundInvoke(InvocationContext ctx) throws Exception { - Object bindings = ctx.getContextData().get(InvocationContextImpl.KEY_INTERCEPTOR_BINDINGS); + Object bindings = ctx.getContextData().get(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS); if (bindings == null) { throw new IllegalArgumentException("No bindings found"); } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/InvocationContextBindingsTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/InvocationContextBindingsTest.java index 35c35de066479..b1a2e70027016 100644 --- a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/InvocationContextBindingsTest.java +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/bindings/InvocationContextBindingsTest.java @@ -4,7 +4,7 @@ import io.quarkus.arc.Arc; import io.quarkus.arc.ArcContainer; -import io.quarkus.arc.InvocationContextImpl; +import io.quarkus.arc.ArcInvocationContext; import io.quarkus.arc.test.ArcTestContainer; import io.quarkus.arc.test.interceptors.Simple; import javax.annotation.Priority; @@ -52,7 +52,7 @@ public static class SimpleInterceptor { @AroundInvoke Object mySuperCoolAroundInvoke(InvocationContext ctx) throws Exception { - Object bindings = ctx.getContextData().get(InvocationContextImpl.KEY_INTERCEPTOR_BINDINGS); + Object bindings = ctx.getContextData().get(ArcInvocationContext.KEY_INTERCEPTOR_BINDINGS); if (bindings != null) { return bindings.toString() + "::" + ctx.proceed(); } diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/context/AsyncContinuationTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/context/AsyncContinuationTest.java new file mode 100644 index 0000000000000..5f5e3fd959dfb --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/context/AsyncContinuationTest.java @@ -0,0 +1,109 @@ +package io.quarkus.arc.test.interceptors.context; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; +import io.quarkus.arc.test.interceptors.Simple; +import java.io.IOException; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import javax.annotation.Priority; +import javax.inject.Singleton; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class AsyncContinuationTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Simple.class, SimpleBean.class, + AlphaInterceptor.class, BravoInterceptor.class, CharlieInterceptor.class); + + private static ExecutorService executor; + + @BeforeAll + static void init() { + executor = Executors.newFixedThreadPool(1); + } + + @AfterAll + static void tearDown() { + executor.shutdownNow(); + } + + @Test + public void testAsyncExecution() throws IOException, InterruptedException { + BravoInterceptor.reset(); + assertEquals("A1:dummy:A2", Arc.container().instance(SimpleBean.class).get().foo()); + assertTrue(BravoInterceptor.latch.await(3, TimeUnit.SECONDS)); + assertEquals("C1:ok:C2", BravoInterceptor.asyncResult); + } + + @Simple + @Singleton + static class SimpleBean { + + String foo() { + return "ok"; + } + + } + + @Simple + @Priority(1) + @Interceptor + public static class AlphaInterceptor { + + @AroundInvoke + Object around(InvocationContext ctx) throws Exception { + return "A1:" + ctx.proceed() + ":A2"; + } + } + + @Simple + @Priority(2) + @Interceptor + public static class BravoInterceptor { + + static CountDownLatch latch; + static String asyncResult; + + static void reset() { + latch = new CountDownLatch(1); + asyncResult = null; + } + + @AroundInvoke + Object around(InvocationContext ctx) throws Exception { + executor.submit(() -> { + try { + asyncResult = ctx.proceed().toString(); + latch.countDown(); + } catch (Exception e) { + throw new RuntimeException(); + } + }); + return "dummy"; + } + } + + @Simple + @Priority(3) + @Interceptor + public static class CharlieInterceptor { + + @AroundInvoke + Object around(InvocationContext ctx) throws Exception { + return "C1:" + ctx.proceed() + ":C2"; + } + } + +} diff --git a/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/context/ContextDataTest.java b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/context/ContextDataTest.java new file mode 100644 index 0000000000000..35231502036e9 --- /dev/null +++ b/independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/interceptors/context/ContextDataTest.java @@ -0,0 +1,62 @@ +package io.quarkus.arc.test.interceptors.context; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.test.ArcTestContainer; +import io.quarkus.arc.test.interceptors.Simple; +import java.io.IOException; +import javax.annotation.Priority; +import javax.inject.Singleton; +import javax.interceptor.AroundInvoke; +import javax.interceptor.Interceptor; +import javax.interceptor.InvocationContext; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +public class ContextDataTest { + + @RegisterExtension + public ArcTestContainer container = new ArcTestContainer(Simple.class, SimpleBean.class, + AlphaInterceptor.class, BravoInterceptor.class); + + @Test + public void testContextData() throws IOException { + assertEquals("alpha:bravo:ok:true", Arc.container().instance(SimpleBean.class).get().foo()); + } + + @Simple + @Singleton + static class SimpleBean { + + String foo() { + return "ok"; + } + + } + + @Simple + @Priority(1) + @Interceptor + public static class AlphaInterceptor { + + @AroundInvoke + Object around(InvocationContext ctx) throws Exception { + Object ret = "alpha:" + ctx.proceed(); + return ret + ":" + ctx.getContextData().get("bravo"); + } + } + + @Simple + @Priority(2) + @Interceptor + public static class BravoInterceptor { + + @AroundInvoke + Object around(InvocationContext ctx) throws Exception { + ctx.getContextData().put("bravo", true); + return "bravo:" + ctx.proceed(); + } + } + +}