diff --git a/.github/workflows/ci-actions.yml b/.github/workflows/ci-actions.yml index 3e5b57c2ac..337f82d271 100644 --- a/.github/workflows/ci-actions.yml +++ b/.github/workflows/ci-actions.yml @@ -53,7 +53,7 @@ jobs: run: | JBOSS_HOME=`pwd`'/container/*' export JBOSS_HOME=`echo $JBOSS_HOME` - mvn clean package -Pupdate-jboss-as -Dtck -f jboss-as/pom.xml + mvn clean package -Pupdate-jboss-as -Pupdate-jakarta-apis -Dtck -f jboss-as/pom.xml - name: Zip Patched WildFly run: | cd container/ diff --git a/impl/src/main/java/org/jboss/weld/bean/AbstractClassBean.java b/impl/src/main/java/org/jboss/weld/bean/AbstractClassBean.java index b65eb0c538..f30c577a0d 100644 --- a/impl/src/main/java/org/jboss/weld/bean/AbstractClassBean.java +++ b/impl/src/main/java/org/jboss/weld/bean/AbstractClassBean.java @@ -16,10 +16,12 @@ */ package org.jboss.weld.bean; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; +import jakarta.enterprise.inject.spi.AnnotatedMethod; import jakarta.enterprise.inject.spi.BeanAttributes; import jakarta.enterprise.inject.spi.Decorator; import jakarta.enterprise.inject.spi.InjectionPoint; @@ -49,6 +51,8 @@ public abstract class AbstractClassBean extends AbstractBean> imp protected final SlimAnnotatedType annotatedType; protected volatile EnhancedAnnotatedType enhancedAnnotatedItem; + protected Collection> invokableMethods; + // Injection target for the bean private InjectionTarget producer; diff --git a/impl/src/main/java/org/jboss/weld/bootstrap/events/ProcessManagedBeanImpl.java b/impl/src/main/java/org/jboss/weld/bootstrap/events/ProcessManagedBeanImpl.java index 60614aeb06..3e75a69b5d 100644 --- a/impl/src/main/java/org/jboss/weld/bootstrap/events/ProcessManagedBeanImpl.java +++ b/impl/src/main/java/org/jboss/weld/bootstrap/events/ProcessManagedBeanImpl.java @@ -18,10 +18,14 @@ import java.lang.reflect.Type; +import jakarta.enterprise.inject.spi.AnnotatedMethod; import jakarta.enterprise.inject.spi.AnnotatedType; import jakarta.enterprise.inject.spi.ProcessManagedBean; +import jakarta.enterprise.invoke.Invoker; +import jakarta.enterprise.invoke.InvokerBuilder; import org.jboss.weld.bean.ManagedBean; +import org.jboss.weld.invokable.InvokerBuilderImpl; import org.jboss.weld.manager.BeanManagerImpl; public class ProcessManagedBeanImpl extends AbstractProcessClassBean> implements ProcessManagedBean { @@ -42,4 +46,10 @@ public AnnotatedType getAnnotatedBeanClass() { return getBean().getAnnotated(); } + @Override + public InvokerBuilder> createInvoker(AnnotatedMethod annotatedMethod) { + checkWithinObserverNotification(); + return new InvokerBuilderImpl<>(getBean().getType(), annotatedMethod.getJavaMember(), getBeanManager()); + } + } diff --git a/impl/src/main/java/org/jboss/weld/bootstrap/events/ProcessSessionBeanImpl.java b/impl/src/main/java/org/jboss/weld/bootstrap/events/ProcessSessionBeanImpl.java index 494e306838..f499491fbc 100644 --- a/impl/src/main/java/org/jboss/weld/bootstrap/events/ProcessSessionBeanImpl.java +++ b/impl/src/main/java/org/jboss/weld/bootstrap/events/ProcessSessionBeanImpl.java @@ -18,11 +18,15 @@ import java.lang.reflect.Type; +import jakarta.enterprise.inject.spi.AnnotatedMethod; import jakarta.enterprise.inject.spi.AnnotatedType; import jakarta.enterprise.inject.spi.ProcessSessionBean; import jakarta.enterprise.inject.spi.SessionBeanType; +import jakarta.enterprise.invoke.Invoker; +import jakarta.enterprise.invoke.InvokerBuilder; import org.jboss.weld.bean.SessionBean; +import org.jboss.weld.invokable.InvokerBuilderImpl; import org.jboss.weld.logging.BootstrapLogger; import org.jboss.weld.manager.BeanManagerImpl; @@ -63,4 +67,10 @@ public AnnotatedType getAnnotatedBeanClass() { return getBean().getAnnotated(); } + @Override + public InvokerBuilder> createInvoker(AnnotatedMethod annotatedMethod) { + checkWithinObserverNotification(); + return new InvokerBuilderImpl<>(getBean().getEjbDescriptor().getBeanClass(), annotatedMethod.getJavaMember(), + getBeanManager()); + } } diff --git a/impl/src/main/java/org/jboss/weld/invokable/AbstractInvokerBuilder.java b/impl/src/main/java/org/jboss/weld/invokable/AbstractInvokerBuilder.java new file mode 100644 index 0000000000..a755d76ddc --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/invokable/AbstractInvokerBuilder.java @@ -0,0 +1,334 @@ +package org.jboss.weld.invokable; + +import java.lang.annotation.Annotation; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.lang.reflect.Type; + +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.invoke.InvokerBuilder; + +// TODO deployment-time validation of configured lookups +abstract class AbstractInvokerBuilder implements InvokerBuilder { + + final Class beanClass; + // work with reflection representation so that we can re-use this logic from within BCE + final Method method; + + boolean instanceLookup; + boolean[] argLookup; + TransformerMetadata instanceTransformer; + TransformerMetadata[] argTransformers; + TransformerMetadata returnValueTransformer; + TransformerMetadata exceptionTransformer; + TransformerMetadata invocationWrapper; + + final BeanManager beanManager; + + public AbstractInvokerBuilder(Class beanClass, Method method, BeanManager beanManager) { + this.beanClass = beanClass; + this.method = method; + this.argLookup = new boolean[method.getParameterCount()]; + this.argTransformers = new TransformerMetadata[method.getParameterCount()]; + this.beanManager = beanManager; + } + + @Override + public InvokerBuilder setInstanceLookup() { + this.instanceLookup = true; + return this; + } + + @Override + public InvokerBuilder setArgumentLookup(int position) { + if (position >= argLookup.length) { + // TODO better exception + throw new IllegalArgumentException("Error attempting to set CDI argument lookup for arg number " + position + + " while the number of method args is " + argLookup.length); + } + argLookup[position] = true; + return this; + } + + @Override + public InvokerBuilder setInstanceTransformer(Class clazz, String methodName) { + if (instanceTransformer != null) { + // TODO better exception + throw new IllegalStateException("Instance transformer already set"); + } + this.instanceTransformer = new TransformerMetadata(clazz, methodName, TransformerType.INSTANCE); + return this; + } + + @Override + public InvokerBuilder setArgumentTransformer(int position, Class clazz, String methodName) { + if (position >= argTransformers.length) { + // TODO better exception + throw new IllegalArgumentException("Error attempting to set an argument lookup. Number of method args: " + + argLookup.length + " arg position: " + position); + } + if (argTransformers[position] != null) { + // TODO better exception + throw new IllegalStateException("Argument transformer " + position + " already set"); + } + this.argTransformers[position] = new TransformerMetadata(clazz, methodName, TransformerType.ARGUMENT); + return this; + } + + @Override + public InvokerBuilder setReturnValueTransformer(Class clazz, String methodName) { + if (returnValueTransformer != null) { + // TODO better exception + throw new IllegalStateException("Return value transformer already set"); + } + this.returnValueTransformer = new TransformerMetadata(clazz, methodName, TransformerType.RETURN_VALUE); + return this; + } + + @Override + public InvokerBuilder setExceptionTransformer(Class clazz, String methodName) { + if (exceptionTransformer != null) { + // TODO better exception + throw new IllegalStateException("Exception transformer already set"); + } + this.exceptionTransformer = new TransformerMetadata(clazz, methodName, TransformerType.EXCEPTION); + return this; + } + + @Override + public InvokerBuilder setInvocationWrapper(Class clazz, String methodName) { + if (invocationWrapper != null) { + // TODO better exception + throw new IllegalStateException("Invocation wrapper already set"); + } + this.invocationWrapper = new TransformerMetadata(clazz, methodName, TransformerType.WRAPPER); + return this; + } + + private boolean requiresCleanup() { + boolean isStaticMethod = Modifier.isStatic(method.getModifiers()); + if (instanceTransformer != null && !isStaticMethod) { + return true; + } + for (int i = 0; i < argTransformers.length; i++) { + if (argTransformers[i] != null) { + return true; + } + } + if (instanceLookup && !isStaticMethod) { + return true; + } + for (int i = 0; i < argLookup.length; i++) { + if (argLookup[i]) { + return true; + } + } + return false; + } + + InvokerImpl doBuild() { + boolean isStaticMethod = Modifier.isStatic(method.getModifiers()); + int instanceArguments = isStaticMethod ? 0 : 1; + boolean requiresCleanup = requiresCleanup(); + + MethodHandle mh = MethodHandleUtils.createMethodHandle(method); + + // instance transformer + if (instanceTransformer != null && !isStaticMethod) { + MethodHandle instanceTransformerMethod = MethodHandleUtils.createMethodHandleFromTransformer(method, + instanceTransformer, beanClass); + if (instanceTransformerMethod.type().parameterCount() == 1) { // no cleanup + mh = MethodHandles.filterArguments(mh, 0, instanceTransformerMethod); + } else if (instanceTransformerMethod.type().parameterCount() == 2) { // cleanup + instanceTransformerMethod = instanceTransformerMethod + .asType(instanceTransformerMethod.type().changeParameterType(1, CleanupActions.class)); + mh = MethodHandles.collectArguments(mh, 0, instanceTransformerMethod); + instanceArguments++; + } else { + // internal error, this should not pass validation + throw new IllegalStateException("Invalid instance transformer method: " + instanceTransformer); + } + } + + // argument transformers + // backwards iteration for correct construction of the resulting parameter list + for (int i = argTransformers.length - 1; i >= 0; i--) { + if (argTransformers[i] == null) { + continue; + } + int position = instanceArguments + i; + MethodHandle argTransformerMethod = MethodHandleUtils.createMethodHandleFromTransformer(method, + argTransformers[i], method.getParameterTypes()[i]); + if (argTransformerMethod.type().parameterCount() == 1) { // no cleanup + mh = MethodHandles.filterArguments(mh, position, argTransformerMethod); + } else if (argTransformerMethod.type().parameterCount() == 2) { // cleanup + argTransformerMethod = argTransformerMethod + .asType(argTransformerMethod.type().changeParameterType(1, CleanupActions.class)); + mh = MethodHandles.collectArguments(mh, position, argTransformerMethod); + } else { + // internal error, this should not pass validation + throw new IllegalStateException("Invalid argument transformer method: " + argTransformers[i]); + } + } + + // return type transformer + if (returnValueTransformer != null) { + MethodHandle returnValueTransformerMethod = MethodHandleUtils.createMethodHandleFromTransformer(method, + returnValueTransformer, method.getReturnType()); + mh = MethodHandles.filterReturnValue(mh, returnValueTransformerMethod); + } + + // exception transformer + if (exceptionTransformer != null) { + MethodHandle exceptionTransformerMethod = MethodHandleUtils.createMethodHandleFromTransformer(method, + exceptionTransformer, Throwable.class); + mh = MethodHandles.catchException(mh, Throwable.class, exceptionTransformerMethod); + } + + // argument reshuffling to support cleanup tasks for input transformers + // + // for each input that has a transformer with cleanup, the corresponding argument + // has a second argument inserted immediately after it, the `CleanupActions` instance; + // application of the transformer replaces the two arguments with the result + // + // inputs without transformations, or with transformations without cleanup, are left + // intact and application of the transformer only replaces the single argument + if (requiresCleanup) { + MethodType incomingType = MethodType.methodType(mh.type().returnType(), CleanupActions.class); + for (Class paramType : mh.type().parameterArray()) { + if (paramType != CleanupActions.class) { + incomingType = incomingType.appendParameterTypes(paramType); + } + } + int[] reordering = new int[mh.type().parameterCount()]; + int paramCounter = 1; + for (int i = 0; i < reordering.length; i++) { + if (mh.type().parameterType(i) == CleanupActions.class) { + reordering[i] = 0; + } else { + reordering[i] = paramCounter; + paramCounter++; + } + } + mh = MethodHandles.permuteArguments(mh, incomingType, reordering); + } + + MethodType typeBeforeLookups = mh.type(); + int positionsBeforeArguments = 1; // first `CleanupActions` we need to preserve for transformations + if (!isStaticMethod) { + positionsBeforeArguments++; // the target instance + } + + // instance lookup + if (instanceLookup && !isStaticMethod) { + Type type = beanClass; + Class parameterType = typeBeforeLookups.parameterType(1); + Annotation[] qualifiers = LookupUtils.classQualifiers(beanClass, beanManager); + MethodHandle instanceLookupMethod = MethodHandleUtils.LOOKUP; + instanceLookupMethod = MethodHandles.insertArguments(instanceLookupMethod, 1, beanManager, type, qualifiers); + instanceLookupMethod = instanceLookupMethod.asType(instanceLookupMethod.type().changeReturnType(parameterType)); + instanceLookupMethod = MethodHandles.dropArguments(instanceLookupMethod, 0, parameterType); + mh = MethodHandles.collectArguments(mh, 1, instanceLookupMethod); + positionsBeforeArguments++; // second `CleanupActions` + } + + // arguments lookup + // backwards iteration for correct construction of the resulting parameter list + for (int i = argLookup.length - 1; i >= 0; i--) { + if (!argLookup[i]) { + continue; + } + int position = positionsBeforeArguments + i; + Parameter parameter = method.getParameters()[i]; + Type type = parameter.getParameterizedType(); + Class parameterType = typeBeforeLookups.parameterType(i + (isStaticMethod ? 1 : 2)); + Annotation[] qualifiers = LookupUtils.parameterQualifiers(parameter, beanManager); + MethodHandle argumentLookupMethod = MethodHandleUtils.LOOKUP; + argumentLookupMethod = MethodHandles.insertArguments(argumentLookupMethod, 1, beanManager, type, qualifiers); + argumentLookupMethod = argumentLookupMethod.asType(argumentLookupMethod.type().changeReturnType(parameterType)); + argumentLookupMethod = MethodHandles.dropArguments(argumentLookupMethod, 0, parameterType); + mh = MethodHandles.collectArguments(mh, position, argumentLookupMethod); + } + + // argument reshuffling to support cleanup tasks for input lookups + // + // for each input that has a lookup, the corresponding argument + // has a second argument inserted immediately after it, the `CleanupActions` instance; + // application of the lookup replaces the two arguments with the result + // + // inputs without lookup are left intact and application of the transformer + // only replaces the single argument + if (requiresCleanup) { + int[] reordering = new int[mh.type().parameterCount()]; + int paramCounter = 1; + for (int i = 0; i < reordering.length; i++) { + if (mh.type().parameterType(i) == CleanupActions.class) { + reordering[i] = 0; + } else { + reordering[i] = paramCounter; + paramCounter++; + } + } + mh = MethodHandles.permuteArguments(mh, typeBeforeLookups, reordering); + } + + // cleanup + if (requiresCleanup) { + MethodHandle cleanupMethod = mh.type().returnType() == void.class + ? MethodHandleUtils.CLEANUP_FOR_VOID + : MethodHandleUtils.CLEANUP_FOR_NONVOID; + + if (mh.type().returnType() != void.class) { + cleanupMethod = cleanupMethod.asType(cleanupMethod.type() + .changeReturnType(mh.type().returnType()) + .changeParameterType(1, mh.type().returnType())); + } + + mh = MethodHandles.tryFinally(mh, cleanupMethod); + } + + // spread argument array into individual arguments + // keep leading arguments: `CleanupAction` if needed target instance if exists + int leadingArgumentsToKeep = (requiresCleanup ? 1 : 0) + (isStaticMethod ? 0 : 1); + if (isStaticMethod) { + MethodHandle invoker = MethodHandles.spreadInvoker(mh.type(), leadingArgumentsToKeep); + invoker = MethodHandles.insertArguments(invoker, 0, mh); + invoker = MethodHandles.dropArguments(invoker, requiresCleanup ? 1 : 0, Object.class); + mh = invoker; + } else { + MethodHandle invoker = MethodHandles.spreadInvoker(mh.type(), leadingArgumentsToKeep); + invoker = MethodHandles.insertArguments(invoker, 0, mh); + mh = invoker; + } + + // replace `null` values in the arguments array with zero values + // on positions where the method accepts a primitive type + Class[] parameterTypes = method.getParameterTypes(); + if (PrimitiveUtils.hasPrimitive(parameterTypes)) { + MethodHandle replacePrimitiveNulls = MethodHandleUtils.REPLACE_PRIMITIVE_NULLS; + replacePrimitiveNulls = MethodHandles.insertArguments(replacePrimitiveNulls, 1, (Object) parameterTypes); + mh = MethodHandles.filterArguments(mh, requiresCleanup ? 2 : 1, replacePrimitiveNulls); + } + + // instantiate `CleanupActions` + if (requiresCleanup) { + mh = MethodHandles.foldArguments(mh, MethodHandleUtils.CLEANUP_ACTIONS_CTOR); + } + + // create an inner invoker and pass it to wrapper + if (invocationWrapper != null) { + InvokerImpl invoker = new InvokerImpl<>(mh); + + MethodHandle invocationWrapperMethod = MethodHandleUtils.createMethodHandleFromTransformer(method, + invocationWrapper, beanClass); + + mh = MethodHandles.insertArguments(invocationWrapperMethod, 2, invoker); + } + + return new InvokerImpl<>(mh); + } +} diff --git a/impl/src/main/java/org/jboss/weld/invokable/CleanupActions.java b/impl/src/main/java/org/jboss/weld/invokable/CleanupActions.java new file mode 100644 index 0000000000..c0edfa0ddf --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/invokable/CleanupActions.java @@ -0,0 +1,49 @@ +package org.jboss.weld.invokable; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import jakarta.enterprise.context.Dependent; +import jakarta.enterprise.inject.Instance; + +class CleanupActions implements Consumer { + private final List cleanupTasks = new ArrayList<>(); + private final List> dependentInstances = new ArrayList<>(); + + @Override + public void accept(Runnable runnable) { + cleanupTasks.add(runnable); + } + + public void addInstanceHandle(Instance.Handle handle) { + if (handle.getBean().getScope().equals(Dependent.class)) { + dependentInstances.add(handle); + } + } + + public void cleanup() { + // run all registered tasks + for (Runnable r : cleanupTasks) { + r.run(); + } + cleanupTasks.clear(); + + // destroy dependent beans we created + for (Instance.Handle handle : dependentInstances) { + handle.destroy(); + } + dependentInstances.clear(); + } + + // this signature fits into the `MethodHandles.tryFinally()` combinator in case of non-`void` methods + public static R run(Throwable cause, R returnValue, CleanupActions cleanupActions) { + cleanupActions.cleanup(); + return returnValue; + } + + // this signature fits into the `MethodHandles.tryFinally()` combinator in case of `void` methods + public static void run(Throwable cause, CleanupActions cleanupActions) { + cleanupActions.cleanup(); + } +} diff --git a/impl/src/main/java/org/jboss/weld/invokable/InvokerBuilderImpl.java b/impl/src/main/java/org/jboss/weld/invokable/InvokerBuilderImpl.java new file mode 100644 index 0000000000..7503a15cb5 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/invokable/InvokerBuilderImpl.java @@ -0,0 +1,18 @@ +package org.jboss.weld.invokable; + +import java.lang.reflect.Method; + +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.invoke.Invoker; + +public class InvokerBuilderImpl extends AbstractInvokerBuilder> { + + public InvokerBuilderImpl(Class beanClass, Method method, BeanManager beanManager) { + super(beanClass, method, beanManager); + } + + @Override + public Invoker build() { + return doBuild(); + } +} diff --git a/impl/src/main/java/org/jboss/weld/invokable/InvokerImpl.java b/impl/src/main/java/org/jboss/weld/invokable/InvokerImpl.java new file mode 100644 index 0000000000..cd6a300e1e --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/invokable/InvokerImpl.java @@ -0,0 +1,26 @@ +package org.jboss.weld.invokable; + +import java.lang.invoke.MethodHandle; + +import jakarta.enterprise.inject.build.compatible.spi.InvokerInfo; +import jakarta.enterprise.invoke.Invoker; + +public class InvokerImpl implements Invoker, InvokerInfo { + private final MethodHandle mh; + + InvokerImpl(MethodHandle mh) { + this.mh = mh; + } + + @Override + public R invoke(T instance, Object[] arguments) { + try { + return (R) mh.invoke(instance, arguments); + } catch (ValueCarryingException e) { + // exception transformer may return a value by throwing a special exception + return (R) e.getMethodReturnValue(); + } catch (Throwable e) { + throw SneakyThrow.sneakyThrow(e); + } + } +} diff --git a/impl/src/main/java/org/jboss/weld/invokable/InvokerInfoBuilder.java b/impl/src/main/java/org/jboss/weld/invokable/InvokerInfoBuilder.java new file mode 100644 index 0000000000..613816c645 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/invokable/InvokerInfoBuilder.java @@ -0,0 +1,18 @@ +package org.jboss.weld.invokable; + +import java.lang.reflect.Method; + +import jakarta.enterprise.inject.build.compatible.spi.InvokerInfo; +import jakarta.enterprise.inject.spi.BeanManager; + +public class InvokerInfoBuilder extends AbstractInvokerBuilder { + + public InvokerInfoBuilder(Class beanClass, Method method, BeanManager beanManager) { + super(beanClass, method, beanManager); + } + + @Override + public InvokerInfo build() { + return doBuild(); + } +} diff --git a/impl/src/main/java/org/jboss/weld/invokable/LookupUtils.java b/impl/src/main/java/org/jboss/weld/invokable/LookupUtils.java new file mode 100644 index 0000000000..c6ccc46c2a --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/invokable/LookupUtils.java @@ -0,0 +1,48 @@ +package org.jboss.weld.invokable; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Parameter; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import jakarta.enterprise.inject.Default; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.inject.Named; + +import org.jboss.weld.inject.WeldInstance; + +class LookupUtils { + private LookupUtils() { + } + + static Object lookup(CleanupActions cleanup, BeanManager beanManager, Type type, Annotation[] qualifiers) { + WeldInstance lookup = (WeldInstance) beanManager.createInstance(); + Instance.Handle result = lookup.select(type, qualifiers).getHandle(); + cleanup.addInstanceHandle(result); + return result.get(); + } + + static Annotation[] classQualifiers(Class beanClass, BeanManager bm) { + return findQualifiers(beanClass.getAnnotations(), bm); + } + + static Annotation[] parameterQualifiers(Parameter parameter, BeanManager bm) { + return findQualifiers(parameter.getAnnotations(), bm); + } + + private static Annotation[] findQualifiers(Annotation[] annotations, BeanManager bm) { + List qualifiers = new ArrayList<>(); + for (Annotation a : annotations) { + if (bm.isQualifier(a.annotationType())) { + qualifiers.add(a); + } + } + // add @Default when there are no qualifiers or just @Named + if (qualifiers.isEmpty() || (qualifiers.size() == 1 && qualifiers.get(0).annotationType().equals(Named.class))) { + qualifiers.add(Default.Literal.INSTANCE); + } + return qualifiers.toArray(Annotation[]::new); + } +} diff --git a/impl/src/main/java/org/jboss/weld/invokable/MethodHandleUtils.java b/impl/src/main/java/org/jboss/weld/invokable/MethodHandleUtils.java new file mode 100644 index 0000000000..61ac07769a --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/invokable/MethodHandleUtils.java @@ -0,0 +1,215 @@ +package org.jboss.weld.invokable; + +import java.lang.annotation.Annotation; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.DeploymentException; +import jakarta.enterprise.invoke.Invoker; + +class MethodHandleUtils { + private MethodHandleUtils() { + } + + static final MethodHandle CLEANUP_ACTIONS_CTOR; + static final MethodHandle CLEANUP_FOR_VOID; + static final MethodHandle CLEANUP_FOR_NONVOID; + static final MethodHandle LOOKUP; + static final MethodHandle REPLACE_PRIMITIVE_NULLS; + static final MethodHandle THROW_VALUE_CARRYING_EXCEPTION; + + static { + try { + CLEANUP_ACTIONS_CTOR = createMethodHandle(CleanupActions.class.getDeclaredConstructor()); + String runName = "run"; + CLEANUP_FOR_VOID = createMethodHandle(CleanupActions.class.getMethod( + runName, Throwable.class, CleanupActions.class)); + CLEANUP_FOR_NONVOID = createMethodHandle(CleanupActions.class.getMethod( + runName, Throwable.class, Object.class, CleanupActions.class)); + LOOKUP = createMethodHandle(LookupUtils.class.getDeclaredMethod( + "lookup", CleanupActions.class, BeanManager.class, Type.class, Annotation[].class)); + REPLACE_PRIMITIVE_NULLS = MethodHandleUtils.createMethodHandle(PrimitiveUtils.class.getDeclaredMethod( + "replacePrimitiveNulls", Object[].class, Class[].class)); + THROW_VALUE_CARRYING_EXCEPTION = createMethodHandle(ValueCarryingException.class.getDeclaredMethod( + "throwReturnValue", Object.class)); + } catch (NoSuchMethodException e) { + // should never happen + throw new IllegalStateException("Unable to locate Weld internal helper method", e); + } + } + + static MethodHandle createMethodHandle(Method method) { + MethodHandles.Lookup lookup = Modifier.isPublic(method.getModifiers()) + && Modifier.isPublic(method.getDeclaringClass().getModifiers()) + ? MethodHandles.publicLookup() + : MethodHandles.lookup(); + + try { + return lookup.unreflect(method); + } catch (ReflectiveOperationException e) { + // TODO proper exception handling + throw new RuntimeException(e); + } + } + + static MethodHandle createMethodHandle(Constructor constructor) { + MethodHandles.Lookup lookup = Modifier.isPublic(constructor.getModifiers()) + && Modifier.isPublic(constructor.getDeclaringClass().getModifiers()) + ? MethodHandles.publicLookup() + : MethodHandles.lookup(); + + try { + return lookup.unreflectConstructor(constructor); + } catch (ReflectiveOperationException e) { + // TODO proper exception handling + throw new RuntimeException(e); + } + } + + static MethodHandle createMethodHandleFromTransformer(Method targetMethod, TransformerMetadata transformer, + Class transformationArgType) { + List matchingMethods = new ArrayList<>(); + // transformers must be `public` and may be inherited (if not `static`) + for (Method m : transformer.getDeclaringClass().getMethods()) { + // `static` transformers must be declared directly on the given class, + // instance transformers may be inherited + if (Modifier.isStatic(m.getModifiers()) && !m.getDeclaringClass().equals(transformer.getDeclaringClass())) { + continue; + } + // method match is only based on class and name, no match is a problem and so are multiple + if (m.getName().equals(transformer.getMethodName())) { + matchingMethods.add(m); + } + } + if (matchingMethods.isEmpty()) { + // TODO better exception + throw new DeploymentException(transformer + ": no method found"); + } + if (matchingMethods.size() > 1) { + // TODO better exception + throw new DeploymentException(transformer + ": more than one method found: " + matchingMethods); + } + Method method = matchingMethods.get(0); + + // validate method + validateTransformerMethod(method, transformer, transformationArgType); + + MethodHandle result = createMethodHandle(method); + + // for input transformers, we might need to change return type to whatever the original method expects + // for output transformers, we might need to change their input params + // this enables transformers to operate on subclasses (input tf) or superclasses (output tf) + if (transformer.isInputTransformer() + && !result.type().returnType().equals(transformationArgType)) { + result = result.asType(result.type().changeReturnType(transformationArgType)); + } else if (transformer.isOutputTransformer() + && result.type().parameterCount() > 0 + && !result.type().parameterType(0).equals(transformationArgType)) { + result = result.asType(result.type().changeParameterType(0, transformationArgType)); + } + if (TransformerType.EXCEPTION.equals(transformer.getType())) { + // if assignable, then just alter return type + if (targetMethod.getReturnType().isAssignableFrom(result.type().returnType())) { + // exception handlers can return a subtype of original class + // so long as it is assignable, just cast it to the required/expected type + result = result.asType(result.type().changeReturnType(targetMethod.getReturnType())); + } else { + // if not assignable, then we need to apply a return value transformer which hides the value in an exception + MethodHandle throwReturnValue = THROW_VALUE_CARRYING_EXCEPTION; + // cast return value of the custom method we use to whatever the original method expects - we'll never use it anyway + throwReturnValue = throwReturnValue + .asType(throwReturnValue.type().changeReturnType(targetMethod.getReturnType())); + // adapt the parameter type as well, we don't really care what it is, we just store it and throw it + throwReturnValue = throwReturnValue + .asType(throwReturnValue.type().changeParameterType(0, result.type().returnType())); + result = MethodHandles.filterReturnValue(result, throwReturnValue); + } + } + return result; + } + + private static void validateTransformerMethod(Method m, TransformerMetadata transformer, Class transformationArgType) { + // all transformers have to be public to ensure accessibility + if (!Modifier.isPublic(m.getModifiers())) { + // TODO better exception + throw new DeploymentException("All invocation transformers need to be public - " + transformer); + } + + int paramCount = m.getParameterCount(); + if (transformer.isInputTransformer()) { + // input transformers need to validate assignability of their return type versus original arg type + if (!transformationArgType.isAssignableFrom(m.getReturnType())) { + // TODO better exception + throw new DeploymentException("Input transformer " + transformer + + " has a return value that is not assignable to expected class: " + transformationArgType); + } + // instance method is no-param, otherwise its 1-2 with second being Consumer + if (!Modifier.isStatic(m.getModifiers())) { + if (paramCount != 0) { + // TODO better exception + throw new DeploymentException( + "Non-static input transformers are expected to have zero input parameters! Transformer: " + + transformer); + } + } else { + if (paramCount > 2) { + // TODO better exception + throw new DeploymentException( + "Static input transformers can only have one or two parameters. " + transformer); + } + if (paramCount == 2) { + // we do not validate type param of Consumer, i.e. if it's exactly Consumer + if (!Consumer.class.equals(m.getParameters()[1].getType())) { + // TODO better exception + throw new DeploymentException( + "Static input transformers with two parameters can only have Consumer as their second parameter! " + + transformer); + } + } + } + } else if (transformer.isOutputTransformer()) { + // output transformers need to validate assignability of their INPUT + // this also means instance methods need no validation in this regard + if (!Modifier.isStatic(m.getModifiers())) { + if (paramCount != 0) { + // TODO better exception + throw new DeploymentException( + "Non-static output transformers are expected to have zero input parameters! Transformer: " + + transformer); + } + } else { + if (paramCount != 1) { + // TODO better exception + throw new DeploymentException( + "Static output transformers are expected to have one input parameter! Transformer: " + transformer); + } + if (!m.getParameters()[0].getType().isAssignableFrom(transformationArgType)) { + // TODO better exception + throw new DeploymentException("Output transformer " + transformer + + " parameter is not assignable to the expected type " + transformationArgType); + } + } + } else { + // wrapper has exactly three arguments + Class[] params = m.getParameterTypes(); + if (params.length == 3 + && params[0].equals(transformationArgType) + && params[1].equals(Object[].class) + && params[2].equals(Invoker.class)) { + // OK + } else { + // TODO better exception + throw new DeploymentException("Invocation wrapper has unexpected parameters " + transformer + + "\nExpected param types are: " + transformationArgType + ", Object[], Invoker.class"); + } + } + } +} diff --git a/impl/src/main/java/org/jboss/weld/invokable/PrimitiveUtils.java b/impl/src/main/java/org/jboss/weld/invokable/PrimitiveUtils.java new file mode 100644 index 0000000000..a9f900687a --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/invokable/PrimitiveUtils.java @@ -0,0 +1,37 @@ +package org.jboss.weld.invokable; + +import java.util.Map; + +class PrimitiveUtils { + private PrimitiveUtils() { + } + + private static final Map, Object> PRIMITIVE_WRAPPER_ZERO_VALUES = Map.ofEntries( + Map.entry(boolean.class, false), + Map.entry(byte.class, (byte) 0), + Map.entry(short.class, (short) 0), + Map.entry(int.class, 0), + Map.entry(long.class, 0L), + Map.entry(float.class, 0.0F), + Map.entry(double.class, 0.0), + Map.entry(char.class, (char) 0)); + + static boolean hasPrimitive(Class[] types) { + for (Class type : types) { + if (type.isPrimitive()) { + return true; + } + } + return false; + } + + static Object[] replacePrimitiveNulls(Object[] values, Class[] types) { + for (int i = 0; i < values.length; i++) { + Class type = types[i]; + if (values[i] == null && type.isPrimitive()) { + values[i] = PRIMITIVE_WRAPPER_ZERO_VALUES.get(type); + } + } + return values; + } +} diff --git a/impl/src/main/java/org/jboss/weld/invokable/SneakyThrow.java b/impl/src/main/java/org/jboss/weld/invokable/SneakyThrow.java new file mode 100644 index 0000000000..4c1e2bc811 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/invokable/SneakyThrow.java @@ -0,0 +1,16 @@ +package org.jboss.weld.invokable; + +class SneakyThrow { + private SneakyThrow() { + } + + /** + * This method can and should be used as part of a {@code throw} statement, + * such as: {@code throw sneakyThrow(exception);}. It is guaranteed to never return normally, + * and this style of usage makes sure that the Java compiler is aware of that. + */ + @SuppressWarnings("unchecked") + static RuntimeException sneakyThrow(Throwable e) throws E { + throw (E) e; + } +} diff --git a/impl/src/main/java/org/jboss/weld/invokable/TransformerMetadata.java b/impl/src/main/java/org/jboss/weld/invokable/TransformerMetadata.java new file mode 100644 index 0000000000..8200d66514 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/invokable/TransformerMetadata.java @@ -0,0 +1,65 @@ +package org.jboss.weld.invokable; + +import org.jboss.weld.exceptions.IllegalStateException; +import org.jboss.weld.util.Preconditions; + +public class TransformerMetadata { + + private final Class declaringClass; + private final String methodName; + private final TransformerType type; + + public TransformerMetadata(Class declaringClass, String methodName, TransformerType type) { + Preconditions.checkArgumentNotNull(declaringClass); + Preconditions.checkArgumentNotNull(methodName); + Preconditions.checkArgumentNotNull(type); + this.declaringClass = declaringClass; + this.methodName = methodName; + this.type = type; + } + + public Class getDeclaringClass() { + return declaringClass; + } + + public String getMethodName() { + return methodName; + } + + public TransformerType getType() { + return type; + } + + public boolean isInputTransformer() { + return type == TransformerType.INSTANCE || type == TransformerType.ARGUMENT; + } + + public boolean isOutputTransformer() { + return type == TransformerType.RETURN_VALUE || type == TransformerType.EXCEPTION; + } + + @Override + public String toString() { + String kind = ""; + switch (this.type) { + case WRAPPER: + kind = "Invocation wrapper "; + break; + case INSTANCE: + kind = "Target instance transformer "; + break; + case ARGUMENT: + kind = "Argument transformer "; + break; + case RETURN_VALUE: + kind = "Return value transformer "; + break; + case EXCEPTION: + kind = "Exception transformer "; + break; + default: + throw new IllegalStateException("Unknown transformer " + type); + } + return kind + declaringClass + "#" + methodName + "()"; + } +} diff --git a/impl/src/main/java/org/jboss/weld/invokable/TransformerType.java b/impl/src/main/java/org/jboss/weld/invokable/TransformerType.java new file mode 100644 index 0000000000..127f0f0cdf --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/invokable/TransformerType.java @@ -0,0 +1,10 @@ +package org.jboss.weld.invokable; + +public enum TransformerType { + + WRAPPER, + INSTANCE, + ARGUMENT, + RETURN_VALUE, + EXCEPTION, +} diff --git a/impl/src/main/java/org/jboss/weld/invokable/ValueCarryingException.java b/impl/src/main/java/org/jboss/weld/invokable/ValueCarryingException.java new file mode 100644 index 0000000000..0075f48bc1 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/invokable/ValueCarryingException.java @@ -0,0 +1,26 @@ +package org.jboss.weld.invokable; + +class ValueCarryingException extends Exception { + + // we use this method to filter return values of exception transformer into excepted types + static Object throwReturnValue(Object returnValue) throws ValueCarryingException { + // rethrow method return type inside exception + throw new ValueCarryingException(returnValue); + } + + private final Object methodReturnValue; + + public ValueCarryingException(Object methodReturnValue) { + this.methodReturnValue = methodReturnValue; + } + + public Object getMethodReturnValue() { + return methodReturnValue; + } + + @Override + public synchronized Throwable fillInStackTrace() { + // no need to capture stack trace, this exception type is only used to carry a return value + return this; + } +} diff --git a/impl/src/main/java/org/jboss/weld/logging/BootstrapLogger.java b/impl/src/main/java/org/jboss/weld/logging/BootstrapLogger.java index d194f033f6..33fcfb13c6 100644 --- a/impl/src/main/java/org/jboss/weld/logging/BootstrapLogger.java +++ b/impl/src/main/java/org/jboss/weld/logging/BootstrapLogger.java @@ -344,4 +344,8 @@ public interface BootstrapLogger extends WeldLogger { @Message(id = 183, value = "Multiple different @Priority values derived from stereotype annotations for annotated type - {0}", format = Format.MESSAGE_FORMAT) DefinitionException multiplePriorityValuesDeclared(Object annotatedType); + @LogMessage(level = Logger.Level.DEBUG) + @Message(id = 184, value = "BeforeBeanDiscovery.addInvokable() called by {0} for {1}", format = Format.MESSAGE_FORMAT) + void addInvokableCalled(Object extensionName, Object type); + } diff --git a/impl/src/main/java/org/jboss/weld/logging/InvokerLogger.java b/impl/src/main/java/org/jboss/weld/logging/InvokerLogger.java new file mode 100644 index 0000000000..4fd6e9cc56 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/logging/InvokerLogger.java @@ -0,0 +1,19 @@ +package org.jboss.weld.logging; + +import static org.jboss.weld.logging.WeldLogger.WELD_PROJECT_CODE; + +import org.jboss.logging.annotations.Message; +import org.jboss.logging.annotations.MessageLogger; +import org.jboss.weld.exceptions.IllegalArgumentException; + +/** + * Log messages for validation related classes. + * + * Message IDs: 002000 - 002099 + */ +@MessageLogger(projectCode = WELD_PROJECT_CODE) +public interface InvokerLogger extends WeldLogger { + + @Message(id = 2000, value = "TBD {0}", format = Message.Format.MESSAGE_FORMAT) + IllegalArgumentException tbd(Object param1); +} diff --git a/impl/src/main/java/org/jboss/weld/util/ForwardingBeanManager.java b/impl/src/main/java/org/jboss/weld/util/ForwardingBeanManager.java index f5e51eafb1..d9e9418fde 100644 --- a/impl/src/main/java/org/jboss/weld/util/ForwardingBeanManager.java +++ b/impl/src/main/java/org/jboss/weld/util/ForwardingBeanManager.java @@ -19,6 +19,7 @@ import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import java.util.Collection; import java.util.List; import java.util.Set; @@ -178,6 +179,11 @@ public Context getContext(Class scopeType) { return delegate().getContext(scopeType); } + @Override + public Collection getContexts(Class scopeType) { + return delegate().getContexts(scopeType); + } + @Override public ELResolver getELResolver() { return delegate().getELResolver(); diff --git a/impl/src/test/java/org/jboss/weld/invokable/Playground_Cleanup.java b/impl/src/test/java/org/jboss/weld/invokable/Playground_Cleanup.java new file mode 100644 index 0000000000..87bc48eb8d --- /dev/null +++ b/impl/src/test/java/org/jboss/weld/invokable/Playground_Cleanup.java @@ -0,0 +1,41 @@ +package org.jboss.weld.invokable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; + +public class Playground_Cleanup { + public static void main(String[] args) throws Throwable { + MethodHandle mh = MethodHandleUtils.createMethodHandle(Playground_Cleanup.class.getMethod("hello", int.class)); + System.out.println("!!!!!!! 1 " + mh.type()); + + mh = MethodHandles.dropArguments(mh, 0, CleanupActions.class); + System.out.println("!!!!!!! 2 " + mh.type()); + + MethodHandle cleanupMethod = mh.type().returnType() == void.class + ? MethodHandleUtils.createMethodHandle( + CleanupActions.class.getDeclaredMethod("run", Throwable.class, CleanupActions.class)) + : MethodHandleUtils.createMethodHandle( + CleanupActions.class.getDeclaredMethod("run", Throwable.class, Object.class, CleanupActions.class)); + System.out.println("cleanup pre-adapt: " + cleanupMethod.type()); + + if (mh.type().returnType() != void.class) { + cleanupMethod = cleanupMethod.asType(cleanupMethod.type() + .changeReturnType(mh.type().returnType()) + .changeParameterType(1, mh.type().returnType())); + } + System.out.println("cleanup post-adapt: " + cleanupMethod.type()); + + mh = MethodHandles.tryFinally(mh, cleanupMethod); + System.out.println("!!!!!!! 3 " + mh.type()); + + CleanupActions cleanup = new CleanupActions(); + cleanup.accept(() -> { + System.out.println("cleanup!"); + }); + System.out.println(mh.invoke(cleanup, new Playground_Cleanup(), 42)); + } + + public String hello(int param) { + return "hello_" + param; + } +} diff --git a/impl/src/test/java/org/jboss/weld/invokable/Playground_Lookup.java b/impl/src/test/java/org/jboss/weld/invokable/Playground_Lookup.java new file mode 100644 index 0000000000..c00253a214 --- /dev/null +++ b/impl/src/test/java/org/jboss/weld/invokable/Playground_Lookup.java @@ -0,0 +1,110 @@ +package org.jboss.weld.invokable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; + +public class Playground_Lookup { + public static void main(String[] args) throws Throwable { + MethodHandle lookupMethod = MethodHandleUtils + .createMethodHandle(Playground_Lookup.class.getMethod("lookup", CleanupActions.class)); + + Method targetMethod = Playground_Lookup.class.getMethod("hello", CleanupActions.class, Playground_Lookup.class, + int.class, long.class, char.class); + boolean isStaticMethod = false; + + MethodHandle mh = MethodHandleUtils.createMethodHandle(targetMethod); + System.out.println("!!!!!!! 1 " + mh.type()); + + MethodType originalType = mh.type(); + + int positionsBeforeArguments = 1; // first `CleanupActions` we need to preserve for transformations + if (!isStaticMethod) { + positionsBeforeArguments++; // the target instance + } + + // instance lookup + boolean instanceLookup = true; + if (instanceLookup && !isStaticMethod) { + Class type = originalType.parameterType(1); + MethodHandle instanceLookupMethod = lookupMethod; + instanceLookupMethod = instanceLookupMethod.asType(instanceLookupMethod.type() + .changeReturnType(type)); + instanceLookupMethod = MethodHandles.dropArguments(instanceLookupMethod, 0, type); + mh = MethodHandles.collectArguments(mh, 1, instanceLookupMethod); + positionsBeforeArguments++; // second `CleanupActions` + } + System.out.println("!!!!!!! 2 " + mh.type()); + + // arguments lookup + // backwards iteration for correct construction of the resulting parameter list + boolean[] argumentsLookup = { true, false, true }; + for (int i = argumentsLookup.length - 1; i >= 0; i--) { + if (argumentsLookup[i]) { + Class type = originalType.parameterType(i + (isStaticMethod ? 1 : 2)); + int position = positionsBeforeArguments + i; + MethodHandle argumentLookupMethod = lookupMethod; + argumentLookupMethod = argumentLookupMethod.asType(argumentLookupMethod.type() + .changeReturnType(type)); + argumentLookupMethod = MethodHandles.dropArguments(argumentLookupMethod, 0, type); + mh = MethodHandles.collectArguments(mh, position, argumentLookupMethod); + } + } + System.out.println("!!!!!!! 3 " + mh.type()); + + // argument reshuffling to support cleanup tasks for input lookups + // + // for each input that has a lookup, the corresponding argument + // has a second argument inserted immediately after it, the `CleanupActions` instance; + // application of the lookup replaces the two arguments with the result + // + // inputs without lookup are left intact and application of the transformer + // only replaces the single argument + { + int[] reordering = new int[mh.type().parameterCount()]; + int paramCounter = 1; + for (int i = 0; i < reordering.length; i++) { + if (mh.type().parameterType(i) == CleanupActions.class) { + reordering[i] = 0; + } else { + reordering[i] = paramCounter; + paramCounter++; + } + } + mh = MethodHandles.permuteArguments(mh, originalType, reordering); + } + + System.out.println("!!!!!!! 4 " + mh.type()); + + CleanupActions cleanup = new CleanupActions(); + System.out.println(mh.invoke(cleanup, new Playground_Lookup(), 0, 13L, '\0')); + } + + // this is a little crazy, but good enough for a playground + // + // due to how the method handle tree is constructed, the `lookup` method + // is called in the following order: + // - arguments first, from left to right + // - target instance last + // + // note that this only applies here; in general, the order of lookups is not specified + private static int lookupCounter = -1; + + public static Object lookup(CleanupActions cleanup) { + lookupCounter++; + if (lookupCounter == 0) { + return 42; + } else if (lookupCounter == 1) { + return 'x'; + } else if (lookupCounter == 2) { + return new Playground_Lookup(); + } else { + throw new AssertionError(); + } + } + + public static String hello(CleanupActions cleanup, Playground_Lookup instance, int param1, long param2, char param3) { + return "hello_" + param1 + "_" + param2 + "_" + param3; + } +} diff --git a/impl/src/test/java/org/jboss/weld/invokable/Playground_Spread.java b/impl/src/test/java/org/jboss/weld/invokable/Playground_Spread.java new file mode 100644 index 0000000000..7b0073e54b --- /dev/null +++ b/impl/src/test/java/org/jboss/weld/invokable/Playground_Spread.java @@ -0,0 +1,53 @@ +package org.jboss.weld.invokable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +public class Playground_Spread { + public static void main(String[] args) throws Throwable { + Method targetMethod = Playground_Spread.class.getMethod("hello", int.class); + boolean isStaticMethod = Modifier.isStatic(targetMethod.getModifiers()); + + MethodHandle mh = MethodHandleUtils.createMethodHandle(targetMethod); + System.out.println("!!!!!!! 1 " + mh.type()); + + mh = MethodHandles.dropArguments(mh, 0, CleanupActions.class); + System.out.println("!!!!!!! 2 " + mh.type()); + + // spread argument array into individual arguments + if (isStaticMethod) { + // keep 1 leading argument, CleanupActions + MethodHandle invoker = MethodHandles.spreadInvoker(mh.type(), 1); + invoker = MethodHandles.insertArguments(invoker, 0, mh); + invoker = MethodHandles.dropArguments(invoker, 1, Object.class); + mh = invoker; + } else { + // keep 2 leading arguments, CleanupActions and the target instance + MethodHandle invoker = MethodHandles.spreadInvoker(mh.type(), 2); + invoker = MethodHandles.insertArguments(invoker, 0, mh); + mh = invoker; + } + System.out.println("!!!!!!! 3 " + mh.type()); + + Class[] parameterTypes = targetMethod.getParameterTypes(); + if (PrimitiveUtils.hasPrimitive(parameterTypes)) { + MethodHandle replacePrimitiveNulls = MethodHandleUtils.createMethodHandle(PrimitiveUtils.class.getDeclaredMethod( + "replacePrimitiveNulls", Object[].class, Class[].class)); + replacePrimitiveNulls = MethodHandles.insertArguments(replacePrimitiveNulls, 1, (Object) parameterTypes); + mh = MethodHandles.filterArguments(mh, 2, replacePrimitiveNulls); + } + System.out.println("!!!!!!! 4 " + mh.type()); + + System.out.println(mh.invoke(new CleanupActions(), new Playground_Spread(), new Object[] { null })); + } + + public String hello(int param) { + return "hello_" + param; + } + + public static String staticHello(int param) { + return "static_hello_" + param; + } +} diff --git a/impl/src/test/java/org/jboss/weld/invokable/Playground_Transformation.java b/impl/src/test/java/org/jboss/weld/invokable/Playground_Transformation.java new file mode 100644 index 0000000000..365b9d3ced --- /dev/null +++ b/impl/src/test/java/org/jboss/weld/invokable/Playground_Transformation.java @@ -0,0 +1,119 @@ +package org.jboss.weld.invokable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.function.Consumer; + +public class Playground_Transformation { + public static void main(String[] args) throws Throwable { + Method targetMethod = Playground_Transformation.class.getMethod("hello", int.class, String.class, int.class); + boolean isStaticMethod = Modifier.isStatic(targetMethod.getModifiers()); + + MethodHandle mh = MethodHandles.lookup().unreflect(targetMethod); + System.out.println("!!!!!!! 1 " + mh.type()); + + int instanceArguments = isStaticMethod ? 0 : 1; + + // target instance + Method instanceTransformer = Playground_Transformation.class.getMethod("transformInstance", + Playground_Transformation.class, Consumer.class); + if (instanceTransformer != null && !isStaticMethod) { + MethodHandle transformer = MethodHandles.lookup().unreflect(instanceTransformer); + if (transformer.type().parameterCount() == 1) { // no cleanup + mh = MethodHandles.filterArguments(mh, 0, transformer); + } else if (transformer.type().parameterCount() == 2) { // cleanup + transformer = transformer.asType(transformer.type().changeParameterType(1, CleanupActions.class)); + mh = MethodHandles.collectArguments(mh, 0, transformer); + instanceArguments++; + } else { + throw new AssertionError(); + } + } + System.out.println("!!!!!!! 2 " + mh.type()); + + // arguments + Method[] argumentTransformers = { + null, //Playground_Transformation.class.getMethod("transform1", int.class, Consumer.class), + Playground_Transformation.class.getMethod("transform2", int.class), + Playground_Transformation.class.getMethod("transform1", int.class, Consumer.class), + }; + for (int i = targetMethod.getParameterCount() - 1; i >= 0; i--) { + if (argumentTransformers[i] != null) { + int position = instanceArguments + i; + MethodHandle transformer = MethodHandles.lookup().unreflect(argumentTransformers[i]); + if (transformer.type().parameterCount() == 1) { // no cleanup + mh = MethodHandles.filterArguments(mh, position, transformer); + } else if (transformer.type().parameterCount() == 2) { // cleanup + transformer = transformer.asType(transformer.type().changeParameterType(1, CleanupActions.class)); + mh = MethodHandles.collectArguments(mh, position, transformer); + } else { + throw new AssertionError(); + } + } + } + System.out.println("!!!!!!! 3 " + mh.type()); + + // return value + Method returnValueTransformer = Playground_Transformation.class.getMethod("transformResult", Object.class); + if (returnValueTransformer != null) { + MethodHandle transformer = MethodHandles.lookup().unreflect(returnValueTransformer); + transformer = transformer.asType(transformer.type().changeParameterType(0, targetMethod.getReturnType())); + mh = MethodHandles.filterReturnValue(mh, transformer); + } + System.out.println("!!!!!!! 4 " + mh.type()); + + MethodType incomingType = MethodType.methodType(mh.type().returnType(), CleanupActions.class); + for (Class paramType : mh.type().parameterArray()) { + if (paramType != CleanupActions.class) { + incomingType = incomingType.appendParameterTypes(paramType); + } + } + int[] reordering = new int[mh.type().parameterCount()]; + int paramCounter = 1; + for (int i = 0; i < reordering.length; i++) { + if (mh.type().parameterType(i) == CleanupActions.class) { + reordering[i] = 0; + } else { + reordering[i] = paramCounter; + paramCounter++; + } + } + mh = MethodHandles.permuteArguments(mh, incomingType, reordering); + System.out.println("!!!!!!! 5 " + mh.type()); + + CleanupActions cleanup = new CleanupActions(); + Object result = mh.invoke(cleanup, new Playground_Transformation(), 10, 20, 30); + System.out.println(Arrays.toString((char[]) result)); + cleanup.cleanup(); + } + + public static Playground_Transformation transformInstance(Playground_Transformation instance, Consumer cleanup) { + cleanup.accept(() -> { + System.out.println("cleanup instance"); + }); + return instance; + } + + public static char[] transformResult(Object value) { + return value.toString().toCharArray(); + } + + public static int transform1(int param, Consumer cleanup) { + cleanup.accept(() -> { + System.out.println("cleanup " + param); + }); + return param + 5; + } + + public static String transform2(int param) { + return "" + (param + 1); + } + + public String hello(int param1, String param2, int param3) { + return "hello_" + param1 + "_" + param2 + "_" + param3; + } +} diff --git a/impl/src/test/java/org/jboss/weld/invokable/Playground_Wrapper.java b/impl/src/test/java/org/jboss/weld/invokable/Playground_Wrapper.java new file mode 100644 index 0000000000..9379701469 --- /dev/null +++ b/impl/src/test/java/org/jboss/weld/invokable/Playground_Wrapper.java @@ -0,0 +1,35 @@ +package org.jboss.weld.invokable; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.util.Arrays; + +import jakarta.enterprise.invoke.Invoker; + +public class Playground_Wrapper { + public static void main(String[] args) throws Throwable { + MethodHandle mh = MethodHandleUtils.createMethodHandle(Playground_Wrapper.class.getMethod("hello", Object[].class)); + System.out.println("!!!!!!! 1 " + mh.type()); + + InvokerImpl invoker = new InvokerImpl<>(mh); + + MethodHandle mh2 = MethodHandleUtils.createMethodHandle( + Playground_Wrapper.class.getMethod("wrap", Playground_Wrapper.class, Object[].class, Invoker.class)); + mh = MethodHandles.insertArguments(mh2, 2, invoker); + System.out.println("!!!!!!! 2 " + mh.type()); + + System.out.println(mh.invoke(new Playground_Wrapper(), new Object[] { 42 })); + } + + public static String wrap(Playground_Wrapper instance, Object[] arguments, Invoker invoker) { + return "wrapped_" + instance + "_" + Arrays.toString(arguments) + "___" + invoker.invoke(instance, arguments); + } + + public String hello(Object[] params) { + return "hello_" + Arrays.toString(params); + } + + public static String staticHello(Object instance, Object[] params) { + return "static_hello_" + instance + "_" + Arrays.toString(params); + } +} diff --git a/jboss-as/pom.xml b/jboss-as/pom.xml index 37ae624355..4bd60fdb9a 100644 --- a/jboss-as/pom.xml +++ b/jboss-as/pom.xml @@ -31,6 +31,9 @@ 6.0.0-SNAPSHOT + + 4.1.0.Alpha1 + 2.2.0-RC1 @@ -69,6 +72,21 @@ cdi-tck-ext-lib ${cdi.tck-4-0.version} + + jakarta.enterprise + jakarta.enterprise.cdi-api + ${cdi.update.version} + + + jakarta.enterprise + jakarta.enterprise.lang-model + ${cdi.update.version} + + + jakarta.interceptor + jakarta.interceptor-api + 2.2.0-RC1 + @@ -188,6 +206,91 @@ + + + + update-jakarta-apis + + package + + + org.apache.maven.plugins + maven-antrun-plugin + + + update-jakarta-apis + package + + run + + + + + + + + + + ========================================================================= + + Updating CDI module and Interceptors API module + + CDI version: ${cdi.update.version} + Interceptors version: ${interceptors.update.version} + + JBOSS_HOME: ${jboss.home} + + ========================================================================= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + install-cdi-tck-ext-lib @@ -239,7 +342,7 @@ ${project.build.directory}/dependency/lib true - cdi-api,weld-api,weld-core-impl,weld-lite-extension-translator,weld-jsf,weld-ejb,weld-jta,weld-web,weld-spi,cdi-tck-ext-lib + jakarta.enterprise.cdi-api,jakarta.enterprise.lang-model,jakarta.interceptor-api,weld-api,weld-core-impl,weld-lite-extension-translator,weld-jsf,weld-ejb,weld-jta,weld-web,weld-spi,cdi-tck-ext-lib diff --git a/pom.xml b/pom.xml index 12ba4a3540..82bc30cba8 100644 --- a/pom.xml +++ b/pom.xml @@ -90,7 +90,7 @@ 2.0.0 3.1.4 7.4.0 - 5.0.SP3 + 6.0.Alpha1 1.0.3.Final 5.0.0.Alpha5 diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/BuildCompatExtension.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/BuildCompatExtension.java new file mode 100644 index 0000000000..5fdb5340ad --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/BuildCompatExtension.java @@ -0,0 +1,295 @@ +package org.jboss.weld.tests.invokable; + +import java.util.Collection; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Instance; +import jakarta.enterprise.inject.build.compatible.spi.BeanInfo; +import jakarta.enterprise.inject.build.compatible.spi.BuildCompatibleExtension; +import jakarta.enterprise.inject.build.compatible.spi.InvokerInfo; +import jakarta.enterprise.inject.build.compatible.spi.Parameters; +import jakarta.enterprise.inject.build.compatible.spi.Registration; +import jakarta.enterprise.inject.build.compatible.spi.Synthesis; +import jakarta.enterprise.inject.build.compatible.spi.SyntheticBeanCreator; +import jakarta.enterprise.inject.build.compatible.spi.SyntheticComponents; +import jakarta.enterprise.invoke.Invoker; +import jakarta.enterprise.lang.model.declarations.MethodInfo; + +import org.jboss.weld.tests.invokable.common.ArgTransformer; +import org.jboss.weld.tests.invokable.common.ExceptionTransformer; +import org.jboss.weld.tests.invokable.common.FooArg; +import org.jboss.weld.tests.invokable.common.InstanceTransformer; +import org.jboss.weld.tests.invokable.common.InvocationWrapper; +import org.jboss.weld.tests.invokable.common.ReturnValueTransformer; +import org.jboss.weld.tests.invokable.common.SimpleBean; +import org.jboss.weld.tests.invokable.common.TransformableBean; +import org.jboss.weld.tests.invokable.common.TrulyExceptionalBean; +import org.junit.Assert; + +public class BuildCompatExtension implements BuildCompatibleExtension { + + // basic invokers, some with lookup + private InvokerInfo noTransformationInvoker; + private InvokerInfo instanceLookupInvoker; + private InvokerInfo argLookupInvoker; + private InvokerInfo lookupAllInvoker; + private InvokerInfo staticNoTransformationInvoker; + private InvokerInfo staticInstanceLookupInvoker; + private InvokerInfo staticArgLookupInvoker; + private InvokerInfo staticLookupAllInvoker; + + // method arg transformers + private InvokerInfo argTransformingInvoker; + private InvokerInfo staticArgTransformingInvoker; + private InvokerInfo argTransformerWithConsumerInvoker; + private InvokerInfo staticArgTransformerWithConsumerInvoker; + + // instance transformers + private InvokerInfo instanceTransformerInvoker; + private InvokerInfo instanceTransformerWithConsumerInvoker; + private InvokerInfo instanceTransformerNoParamInvoker; + + // return value transformers + private InvokerInfo returnTransformerInvoker; + private InvokerInfo returnTransformerNoParamInvoker; + private InvokerInfo staticReturnTransformerInvoker; + private InvokerInfo staticReturnTransformerNoParamInvoker; + + // exception transformers + private InvokerInfo exceptionTransformerInvoker; + private InvokerInfo staticExceptionTransformerInvoker; + + // invocation wrapper + private InvokerInfo invocationWrapperInvoker; + private InvokerInfo staticInvocationWrapperInvoker; + + // Param keys for invokers + // basic invokers, some with lookup + public static String noTransformationInvokerString = "noTransformationInvoker"; + public static String instanceLookupInvokerString = "instanceLookupInvoker"; + public static String argLookupInvokerString = "argLookupInvoker"; + public static String lookupAllInvokerString = "lookupAllInvoker"; + public static String staticNoTransformationInvokerString = "staticNoTransformationInvoker"; + public static String staticInstanceLookupInvokerString = "staticInstanceLookupInvoker"; + public static String staticArgLookupInvokerString = "staticArgLookupInvoker"; + public static String staticLookupAllInvokerString = "staticLookupAllInvoker"; + + // method arg transformers + public static String argTransformingInvokerString = "argTransformingInvoker"; + public static String staticArgTransformingInvokerString = "staticArgTransformingInvoker"; + public static String argTransformerWithConsumerInvokerString = "argTransformerWithConsumerInvoker"; + public static String staticArgTransformerWithConsumerInvokerString = "staticArgTransformerWithConsumerInvoker"; + + // instance transformers + public static String instanceTransformerInvokerString = "instanceTransformerInvoker"; + public static String instanceTransformerWithConsumerInvokerString = "instanceTransformerWithConsumerInvoker"; + public static String instanceTransformerNoParamInvokerString = "instanceTransformerNoParamInvoker"; + + // return value transformers + public static String returnTransformerInvokerString = "returnTransformerInvoker"; + public static String returnTransformerNoParamInvokerString = "returnTransformerNoParamInvoker"; + public static String staticReturnTransformerInvokerString = "staticReturnTransformerInvoker"; + public static String staticReturnTransformerNoParamInvokerString = "staticReturnTransformerNoParamInvoker"; + + // exception transformers + public static String exceptionTransformerInvokerString = "exceptionTransformerInvoker"; + public static String staticExceptionTransformerInvokerString = "staticExceptionTransformerInvoker"; + + // invocation wrapper + public static String invocationWrapperInvokerString = "invocationWrapperInvoker"; + public static String staticInvocationWrapperInvokerString = "staticInvocationWrapperInvoker"; + + @Synthesis + public void synth(SyntheticComponents syntheticComponents) { + // create synth beans that has all the invokers as params + syntheticComponents.addBean(SynthBean.class) + .createWith(SynthBeanCreator.class) + .type(SynthBean.class) + .scope(ApplicationScoped.class) + .withParam(noTransformationInvokerString, noTransformationInvoker) + .withParam(instanceLookupInvokerString, instanceLookupInvoker) + .withParam(argLookupInvokerString, argLookupInvoker) + .withParam(lookupAllInvokerString, lookupAllInvoker) + .withParam(staticNoTransformationInvokerString, staticNoTransformationInvoker) + .withParam(staticInstanceLookupInvokerString, staticInstanceLookupInvoker) + .withParam(staticArgLookupInvokerString, staticArgLookupInvoker) + .withParam(staticLookupAllInvokerString, staticLookupAllInvoker) + .withParam(argTransformingInvokerString, argTransformingInvoker) + .withParam(staticArgTransformingInvokerString, staticArgTransformingInvoker) + .withParam(argTransformerWithConsumerInvokerString, argTransformerWithConsumerInvoker) + .withParam(staticArgTransformerWithConsumerInvokerString, staticArgTransformerWithConsumerInvoker) + .withParam(instanceTransformerInvokerString, instanceTransformerInvoker) + .withParam(instanceTransformerWithConsumerInvokerString, instanceTransformerWithConsumerInvoker) + .withParam(instanceTransformerNoParamInvokerString, instanceTransformerNoParamInvoker) + .withParam(returnTransformerInvokerString, returnTransformerInvoker) + .withParam(returnTransformerNoParamInvokerString, returnTransformerNoParamInvoker) + .withParam(staticReturnTransformerInvokerString, staticReturnTransformerInvoker) + .withParam(staticReturnTransformerNoParamInvokerString, staticReturnTransformerNoParamInvoker) + .withParam(exceptionTransformerInvokerString, exceptionTransformerInvoker) + .withParam(staticExceptionTransformerInvokerString, staticExceptionTransformerInvoker) + .withParam(invocationWrapperInvokerString, invocationWrapperInvoker) + .withParam(staticInvocationWrapperInvokerString, staticInvocationWrapperInvoker); + } + + @Registration(types = SimpleBean.class) + public void createNoTransformationInvokers(BeanInfo b) { + Collection invokableMethods = b.declaringClass().methods(); + Assert.assertEquals(4, invokableMethods.size()); + for (MethodInfo invokableMethod : invokableMethods) { + if (invokableMethod.name().contains("staticPing")) { + staticNoTransformationInvoker = b.createInvoker(invokableMethod).build(); + staticInstanceLookupInvoker = b.createInvoker(invokableMethod).setInstanceLookup().build(); + staticArgLookupInvoker = b.createInvoker(invokableMethod).setArgumentLookup(0).setArgumentLookup(1).build(); + staticLookupAllInvoker = b.createInvoker(invokableMethod).setArgumentLookup(0).setArgumentLookup(1) + .setInstanceLookup().build(); + } else if (invokableMethod.name().contains("ping")) { + noTransformationInvoker = b.createInvoker(invokableMethod).build(); + instanceLookupInvoker = b.createInvoker(invokableMethod).setInstanceLookup().build(); + argLookupInvoker = b.createInvoker(invokableMethod).setArgumentLookup(0).setArgumentLookup(1).build(); + lookupAllInvoker = b.createInvoker(invokableMethod).setArgumentLookup(0).setArgumentLookup(1) + .setInstanceLookup().build(); + } + } + } + + @Registration(types = TransformableBean.class) + public void createArgTransformationInvokers(BeanInfo b) { + Collection invokableMethods = b.declaringClass().methods(); + Assert.assertEquals(4, invokableMethods.size()); + for (MethodInfo invokableMethod : invokableMethods) { + if (invokableMethod.name().contains("staticPing")) { + staticArgTransformingInvoker = b.createInvoker(invokableMethod) + .setArgumentTransformer(0, FooArg.class, "doubleTheString") // non-static transformer method + .setArgumentTransformer(1, ArgTransformer.class, "transform") // static transformer method + .build(); + staticArgTransformerWithConsumerInvoker = b.createInvoker(invokableMethod) + .setArgumentTransformer(0, FooArg.class, "doubleTheString") // non-static transformer method + .setArgumentTransformer(1, ArgTransformer.class, "transform2") // static transformer method with Consumer + .build(); + } else if (invokableMethod.name().contains("ping")) { + argTransformingInvoker = b.createInvoker(invokableMethod) + .setArgumentTransformer(0, FooArg.class, "doubleTheString") // non-static transformer method + .setArgumentTransformer(1, ArgTransformer.class, "transform") // static transformer method + .build(); + argTransformerWithConsumerInvoker = b.createInvoker(invokableMethod) + .setArgumentTransformer(0, FooArg.class, "doubleTheString") // non-static transformer method + .setArgumentTransformer(1, ArgTransformer.class, "transform2") // static transformer method with Consumer + .build(); + } + } + } + + @Registration(types = TransformableBean.class) + public void createInstanceTransformationInvokers(BeanInfo b) { + Collection invokableMethods = b.declaringClass().methods(); + Assert.assertEquals(4, invokableMethods.size()); + for (MethodInfo invokableMethod : invokableMethods) { + if (invokableMethod.name().contains("ping")) { + instanceTransformerInvoker = b.createInvoker(invokableMethod) + .setInstanceTransformer(InstanceTransformer.class, "transform") + .build(); + instanceTransformerWithConsumerInvoker = b.createInvoker(invokableMethod) + .setInstanceTransformer(InstanceTransformer.class, "transform2") + .build(); + instanceTransformerNoParamInvoker = b.createInvoker(invokableMethod) + .setInstanceTransformer(TransformableBean.class, "setTransformed") + .build(); + } + } + } + + @Registration(types = TransformableBean.class) + public void createReturnValueTransformationInvokers(BeanInfo b) { + Collection invokableMethods = b.declaringClass().methods(); + Assert.assertEquals(4, invokableMethods.size()); + for (MethodInfo invokableMethod : invokableMethods) { + if (invokableMethod.name().contains("ping")) { + returnTransformerInvoker = b.createInvoker(invokableMethod) + .setReturnValueTransformer(ReturnValueTransformer.class, "transform") + .build(); + returnTransformerNoParamInvoker = b.createInvoker(invokableMethod) + .setReturnValueTransformer(String.class, "strip") + .build(); + + } else if (invokableMethod.name().contains("staticPing")) { + staticReturnTransformerInvoker = b.createInvoker(invokableMethod) + .setReturnValueTransformer(ReturnValueTransformer.class, "transform") + .build(); + staticReturnTransformerNoParamInvoker = b.createInvoker(invokableMethod) + .setReturnValueTransformer(String.class, "strip") + .build(); + } + } + } + + @Registration(types = TrulyExceptionalBean.class) + public void createExceptionTransformationInvokers(BeanInfo b) { + Collection invokableMethods = b.declaringClass().methods(); + Assert.assertEquals(2, invokableMethods.size()); + for (MethodInfo invokableMethod : invokableMethods) { + if (invokableMethod.name().contains("ping")) { + exceptionTransformerInvoker = b.createInvoker(invokableMethod) + .setExceptionTransformer(ExceptionTransformer.class, "transform") + .build(); + + } else if (invokableMethod.name().contains("staticPing")) { + staticExceptionTransformerInvoker = b.createInvoker(invokableMethod) + .setExceptionTransformer(ExceptionTransformer.class, "transform") + .build(); + } + } + } + + @Registration(types = SimpleBean.class) + public void createInvocationWrapperInvokers(BeanInfo b) { + Collection invokableMethods = b.declaringClass().methods(); + Assert.assertEquals(4, invokableMethods.size()); + for (MethodInfo invokableMethod : invokableMethods) { + if (invokableMethod.name().contains("ping")) { + invocationWrapperInvoker = b.createInvoker(invokableMethod) + .setInvocationWrapper(InvocationWrapper.class, "transform") + .build(); + + } else if (invokableMethod.name().contains("staticPing")) { + staticInvocationWrapperInvoker = b.createInvoker(invokableMethod) + .setInvocationWrapper(InvocationWrapper.class, "transform") + .build(); + } + } + } + + public static class SynthBeanCreator implements SyntheticBeanCreator { + + @Override + public SynthBean create(Instance instance, Parameters parameters) { + SynthBean result = new SynthBean(); + result.setNoTransformationInvoker(parameters.get(noTransformationInvokerString, Invoker.class)); + result.setInstanceLookupInvoker(parameters.get(instanceLookupInvokerString, Invoker.class)); + result.setArgLookupInvoker(parameters.get(argLookupInvokerString, Invoker.class)); + result.setLookupAllInvoker(parameters.get(lookupAllInvokerString, Invoker.class)); + result.setStaticNoTransformationInvoker(parameters.get(staticNoTransformationInvokerString, Invoker.class)); + result.setStaticInstanceLookupInvoker(parameters.get(staticInstanceLookupInvokerString, Invoker.class)); + result.setStaticArgLookupInvoker(parameters.get(staticArgLookupInvokerString, Invoker.class)); + result.setStaticLookupAllInvoker(parameters.get(staticLookupAllInvokerString, Invoker.class)); + result.setArgTransformingInvoker(parameters.get(argTransformingInvokerString, Invoker.class)); + result.setStaticArgTransformingInvoker(parameters.get(staticArgTransformingInvokerString, Invoker.class)); + result.setArgTransformerWithConsumerInvoker(parameters.get(argTransformerWithConsumerInvokerString, Invoker.class)); + result.setStaticArgTransformerWithConsumerInvoker( + parameters.get(staticArgTransformerWithConsumerInvokerString, Invoker.class)); + result.setInstanceTransformerInvoker(parameters.get(instanceTransformerInvokerString, Invoker.class)); + result.setInstanceTransformerWithConsumerInvoker( + parameters.get(instanceTransformerWithConsumerInvokerString, Invoker.class)); + result.setInstanceTransformerNoParamInvoker(parameters.get(instanceTransformerNoParamInvokerString, Invoker.class)); + result.setReturnTransformerInvoker(parameters.get(returnTransformerInvokerString, Invoker.class)); + result.setReturnTransformerNoParamInvoker(parameters.get(returnTransformerNoParamInvokerString, Invoker.class)); + result.setStaticReturnTransformerInvoker(parameters.get(staticReturnTransformerInvokerString, Invoker.class)); + result.setStaticReturnTransformerNoParamInvoker( + parameters.get(staticReturnTransformerNoParamInvokerString, Invoker.class)); + result.setExceptionTransformerInvoker(parameters.get(exceptionTransformerInvokerString, Invoker.class)); + result.setStaticExceptionTransformerInvoker(parameters.get(staticExceptionTransformerInvokerString, Invoker.class)); + result.setInvocationWrapperInvoker(parameters.get(invocationWrapperInvokerString, Invoker.class)); + result.setStaticInvocationWrapperInvoker(parameters.get(staticInvocationWrapperInvokerString, Invoker.class)); + return result; + } + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/InvokableMethodBCETest.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/InvokableMethodBCETest.java new file mode 100644 index 0000000000..0647a3cb8f --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/InvokableMethodBCETest.java @@ -0,0 +1,200 @@ +package org.jboss.weld.tests.invokable; + +import jakarta.enterprise.inject.build.compatible.spi.BuildCompatibleExtension; +import jakarta.inject.Inject; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.BeanArchive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.weld.test.util.Utils; +import org.jboss.weld.tests.invokable.common.ArgTransformer; +import org.jboss.weld.tests.invokable.common.FooArg; +import org.jboss.weld.tests.invokable.common.HelperBean; +import org.jboss.weld.tests.invokable.common.InstanceTransformer; +import org.jboss.weld.tests.invokable.common.SimpleBean; +import org.jboss.weld.tests.invokable.common.TransformableBean; +import org.jboss.weld.tests.invokable.common.TrulyExceptionalBean; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(Arquillian.class) +public class InvokableMethodBCETest { + + @Deployment + public static Archive getDeployment() { + return ShrinkWrap.create(BeanArchive.class, Utils.getDeploymentNameAsHash(InvokableMethodBCETest.class)) + .addPackage(InvokableMethodBCETest.class.getPackage()) + .addPackage(SimpleBean.class.getPackage()) + .addAsServiceProvider(BuildCompatibleExtension.class, BuildCompatExtension.class); + } + + @Inject + SimpleBean simpleBean; + + @Inject + TransformableBean transformableBean; + + @Inject + TrulyExceptionalBean trulyExceptionalBean; + + @Inject + SynthBean synthBean; + + @Test + public void testSimpleInvokableMethod() { + SimpleBean.resetDestroyCounter(); + HelperBean.clearDestroyedCounters(); + + // no transformation invocation, no lookup + Assert.assertEquals("foo1", synthBean.getNoTransformationInvoker().invoke(simpleBean, new Object[] { "foo", 1 })); + Assert.assertEquals(0, SimpleBean.preDestroyInvoked); + Assert.assertEquals(0, HelperBean.timesStringDestroyed); + Assert.assertEquals(0, HelperBean.timesIntDestroyed); + + // no transformation, instance lookup + Assert.assertEquals("foo1", synthBean.getInstanceLookupInvoker().invoke(null, new Object[] { "foo", 1 })); + Assert.assertEquals(1, SimpleBean.preDestroyInvoked); + Assert.assertEquals(0, HelperBean.timesStringDestroyed); + Assert.assertEquals(0, HelperBean.timesIntDestroyed); + + // no transformation, arg lookup (all) + Assert.assertEquals("bar42", synthBean.getArgLookupInvoker().invoke(simpleBean, new Object[] { "blah", null })); + Assert.assertEquals(1, SimpleBean.preDestroyInvoked); + Assert.assertEquals(1, HelperBean.timesStringDestroyed); + Assert.assertEquals(1, HelperBean.timesIntDestroyed); + + // no transformation, instance lookup and arg lookup (all) + Assert.assertEquals("bar42", synthBean.getLookupAllInvoker().invoke(new SimpleBean(), new Object[] { "blah", null })); + Assert.assertEquals(2, SimpleBean.preDestroyInvoked); + Assert.assertEquals(2, HelperBean.timesStringDestroyed); + Assert.assertEquals(2, HelperBean.timesIntDestroyed); + } + + @Test + public void testSimpleStaticInvokableMethod() { + SimpleBean.resetDestroyCounter(); + HelperBean.clearDestroyedCounters(); + + // no transformation invocation, no lookup + Assert.assertEquals("foo1", synthBean.getStaticNoTransformationInvoker().invoke(simpleBean, new Object[] { "foo", 1 })); + Assert.assertEquals(0, SimpleBean.preDestroyInvoked); + Assert.assertEquals(0, HelperBean.timesStringDestroyed); + Assert.assertEquals(0, HelperBean.timesIntDestroyed); + + // no transformation, no instance lookup (configured but skipped, it is a static method) + Assert.assertEquals("foo1", synthBean.getStaticInstanceLookupInvoker().invoke(null, new Object[] { "foo", 1 })); + Assert.assertEquals(0, SimpleBean.preDestroyInvoked); + Assert.assertEquals(0, HelperBean.timesStringDestroyed); + Assert.assertEquals(0, HelperBean.timesIntDestroyed); + + // no transformation, arg lookup (all) + Assert.assertEquals("bar42", synthBean.getStaticArgLookupInvoker().invoke(simpleBean, new Object[] { "blah", null })); + Assert.assertEquals(0, SimpleBean.preDestroyInvoked); + Assert.assertEquals(1, HelperBean.timesStringDestroyed); + Assert.assertEquals(1, HelperBean.timesIntDestroyed); + + // no transformation, no instance lookup (configured but skipped) and arg lookup (all) + Assert.assertEquals("bar42", + synthBean.getStaticLookupAllInvoker().invoke(new SimpleBean(), new Object[] { "blah", null })); + Assert.assertEquals(0, SimpleBean.preDestroyInvoked); + Assert.assertEquals(2, HelperBean.timesStringDestroyed); + Assert.assertEquals(2, HelperBean.timesIntDestroyed); + } + + @Test + public void testArgTransformingInvokableMethod() { + ArgTransformer.runnableExecuted = 0; + + String fooArg = "fooArg"; + String expected = fooArg + fooArg + ArgTransformer.transformed; + Assert.assertEquals(expected, + synthBean.getArgTransformingInvoker().invoke(transformableBean, new Object[] { new FooArg(fooArg), "bar" })); + Assert.assertEquals(expected, + synthBean.getStaticArgTransformingInvoker().invoke(null, new Object[] { new FooArg(fooArg), "bar" })); + + // transformer with Consumer parameter + Assert.assertEquals(0, ArgTransformer.runnableExecuted); + Assert.assertEquals(expected, synthBean.getArgTransformerWithConsumerInvoker().invoke(transformableBean, + new Object[] { new FooArg(fooArg), "bar" })); + Assert.assertEquals(1, ArgTransformer.runnableExecuted); + Assert.assertEquals(expected, synthBean.getStaticArgTransformerWithConsumerInvoker().invoke(null, + new Object[] { new FooArg(fooArg), "bar" })); + Assert.assertEquals(2, ArgTransformer.runnableExecuted); + } + + @Test + public void testInstanceTransformingInvokableMethod() { + InstanceTransformer.runnableExecuted = 0; + + //test is intentionally *NOT* using the bean, but instead passes in new instance every time + String fooArg = "fooArg"; + String expected = (fooArg + fooArg).toUpperCase(); + TransformableBean targetInstance; + + // transformer defined on other class + targetInstance = new TransformableBean(); + Assert.assertFalse(targetInstance.isTransformed()); + Assert.assertEquals(expected, + synthBean.getInstanceTransformerInvoker().invoke(targetInstance, new Object[] { new FooArg(fooArg), fooArg })); + Assert.assertTrue(targetInstance.isTransformed()); + + // transformer defined on other class with Consumer param + targetInstance = new TransformableBean(); + Assert.assertFalse(targetInstance.isTransformed()); + Assert.assertEquals(0, InstanceTransformer.runnableExecuted); + Assert.assertEquals(expected, synthBean.getInstanceTransformerWithConsumerInvoker().invoke(targetInstance, + new Object[] { new FooArg(fooArg), fooArg })); + Assert.assertEquals(1, InstanceTransformer.runnableExecuted); + Assert.assertTrue(targetInstance.isTransformed()); + + // transformer on the bean class, no arg method + targetInstance = new TransformableBean(); + Assert.assertFalse(targetInstance.isTransformed()); + Assert.assertEquals(expected, synthBean.getInstanceTransformerNoParamInvoker().invoke(targetInstance, + new Object[] { new FooArg(fooArg), fooArg })); + Assert.assertTrue(targetInstance.isTransformed()); + } + + @Test + public void testReturnValueTransformingInvokableMethod() { + String fooArg = " fooArg "; + String expected = (fooArg + fooArg).strip(); + + // transformer defined on other class + Assert.assertEquals(expected, + synthBean.getReturnTransformerInvoker().invoke(transformableBean, new Object[] { new FooArg(fooArg), fooArg })); + Assert.assertEquals(expected, synthBean.getStaticReturnTransformerInvoker().invoke(transformableBean, + new Object[] { new FooArg(fooArg), fooArg })); + + // transformer on the result class (String), no arg method + Assert.assertEquals(expected, synthBean.getReturnTransformerNoParamInvoker().invoke(transformableBean, + new Object[] { new FooArg(fooArg), fooArg })); + Assert.assertEquals(expected, synthBean.getStaticReturnTransformerNoParamInvoker().invoke(transformableBean, + new Object[] { new FooArg(fooArg), fooArg })); + } + + @Test + public void testExceptionTransformingInvokableMethod() { + String expected = IllegalArgumentException.class.getSimpleName(); + String expectedStatic = IllegalStateException.class.getSimpleName(); + + // exception transformer can only be defined on other class + Assert.assertEquals(expected, + synthBean.getExceptionTransformerInvoker().invoke(trulyExceptionalBean, new Object[] { "foo", 42 })); + Assert.assertEquals(expectedStatic, + synthBean.getStaticExceptionTransformerInvoker().invoke(trulyExceptionalBean, new Object[] { "foo", 42 })); + } + + @Test + public void testInvocationWrapperInvokableMethod() { + String expected = "foo42foo42"; + + // invocation wrapper defined on other class + Assert.assertEquals(expected, synthBean.getInvocationWrapperInvoker().invoke(simpleBean, new Object[] { "foo", 42 })); + Assert.assertEquals(expected, + synthBean.getStaticInvocationWrapperInvoker().invoke(simpleBean, new Object[] { "foo", 42 })); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/InvokableMethodTest.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/InvokableMethodTest.java new file mode 100644 index 0000000000..b82c9efde9 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/InvokableMethodTest.java @@ -0,0 +1,200 @@ +package org.jboss.weld.tests.invokable; + +import jakarta.enterprise.inject.spi.Extension; +import jakarta.inject.Inject; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.BeanArchive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.weld.test.util.Utils; +import org.jboss.weld.tests.invokable.common.ArgTransformer; +import org.jboss.weld.tests.invokable.common.FooArg; +import org.jboss.weld.tests.invokable.common.HelperBean; +import org.jboss.weld.tests.invokable.common.InstanceTransformer; +import org.jboss.weld.tests.invokable.common.SimpleBean; +import org.jboss.weld.tests.invokable.common.TransformableBean; +import org.jboss.weld.tests.invokable.common.TrulyExceptionalBean; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(Arquillian.class) +public class InvokableMethodTest { + + @Deployment + public static Archive getDeployment() { + return ShrinkWrap.create(BeanArchive.class, Utils.getDeploymentNameAsHash(InvokableMethodTest.class)) + .addPackage(InvokableMethodTest.class.getPackage()) + .addPackage(SimpleBean.class.getPackage()) + .addAsServiceProvider(Extension.class, ObservingExtension.class); + } + + @Inject + ObservingExtension extension; + + @Inject + SimpleBean simpleBean; + + @Inject + TransformableBean transformableBean; + + @Inject + TrulyExceptionalBean trulyExceptionalBean; + + @Test + public void testSimpleInvokableMethod() { + SimpleBean.resetDestroyCounter(); + HelperBean.clearDestroyedCounters(); + + // no transformation invocation, no lookup + Assert.assertEquals("foo1", extension.getNoTransformationInvoker().invoke(simpleBean, new Object[] { "foo", 1 })); + Assert.assertEquals(0, SimpleBean.preDestroyInvoked); + Assert.assertEquals(0, HelperBean.timesStringDestroyed); + Assert.assertEquals(0, HelperBean.timesIntDestroyed); + + // no transformation, instance lookup + Assert.assertEquals("foo1", extension.getInstanceLookupInvoker().invoke(null, new Object[] { "foo", 1 })); + Assert.assertEquals(1, SimpleBean.preDestroyInvoked); + Assert.assertEquals(0, HelperBean.timesStringDestroyed); + Assert.assertEquals(0, HelperBean.timesIntDestroyed); + + // no transformation, arg lookup (all) + Assert.assertEquals("bar42", extension.getArgLookupInvoker().invoke(simpleBean, new Object[] { "blah", null })); + Assert.assertEquals(1, SimpleBean.preDestroyInvoked); + Assert.assertEquals(1, HelperBean.timesStringDestroyed); + Assert.assertEquals(1, HelperBean.timesIntDestroyed); + + // no transformation, instance lookup and arg lookup (all) + Assert.assertEquals("bar42", extension.getLookupAllInvoker().invoke(new SimpleBean(), new Object[] { "blah", null })); + Assert.assertEquals(2, SimpleBean.preDestroyInvoked); + Assert.assertEquals(2, HelperBean.timesStringDestroyed); + Assert.assertEquals(2, HelperBean.timesIntDestroyed); + } + + @Test + public void testSimpleStaticInvokableMethod() { + SimpleBean.resetDestroyCounter(); + HelperBean.clearDestroyedCounters(); + + // no transformation invocation, no lookup + Assert.assertEquals("foo1", extension.getStaticNoTransformationInvoker().invoke(simpleBean, new Object[] { "foo", 1 })); + Assert.assertEquals(0, SimpleBean.preDestroyInvoked); + Assert.assertEquals(0, HelperBean.timesStringDestroyed); + Assert.assertEquals(0, HelperBean.timesIntDestroyed); + + // no transformation, no instance lookup (configured but skipped, it is a static method) + Assert.assertEquals("foo1", extension.getStaticInstanceLookupInvoker().invoke(null, new Object[] { "foo", 1 })); + Assert.assertEquals(0, SimpleBean.preDestroyInvoked); + Assert.assertEquals(0, HelperBean.timesStringDestroyed); + Assert.assertEquals(0, HelperBean.timesIntDestroyed); + + // no transformation, arg lookup (all) + Assert.assertEquals("bar42", extension.getStaticArgLookupInvoker().invoke(simpleBean, new Object[] { "blah", null })); + Assert.assertEquals(0, SimpleBean.preDestroyInvoked); + Assert.assertEquals(1, HelperBean.timesStringDestroyed); + Assert.assertEquals(1, HelperBean.timesIntDestroyed); + + // no transformation, no instance lookup (configured but skipped) and arg lookup (all) + Assert.assertEquals("bar42", + extension.getStaticLookupAllInvoker().invoke(new SimpleBean(), new Object[] { "blah", null })); + Assert.assertEquals(0, SimpleBean.preDestroyInvoked); + Assert.assertEquals(2, HelperBean.timesStringDestroyed); + Assert.assertEquals(2, HelperBean.timesIntDestroyed); + } + + @Test + public void testArgTransformingInvokableMethod() { + ArgTransformer.runnableExecuted = 0; + + String fooArg = "fooArg"; + String expected = fooArg + fooArg + ArgTransformer.transformed; + Assert.assertEquals(expected, + extension.getArgTransformingInvoker().invoke(transformableBean, new Object[] { new FooArg(fooArg), "bar" })); + Assert.assertEquals(expected, + extension.getStaticArgTransformingInvoker().invoke(null, new Object[] { new FooArg(fooArg), "bar" })); + + // transformer with Consumer parameter + Assert.assertEquals(0, ArgTransformer.runnableExecuted); + Assert.assertEquals(expected, extension.getArgTransformerWithConsumerInvoker().invoke(transformableBean, + new Object[] { new FooArg(fooArg), "bar" })); + Assert.assertEquals(1, ArgTransformer.runnableExecuted); + Assert.assertEquals(expected, extension.getStaticArgTransformerWithConsumerInvoker().invoke(null, + new Object[] { new FooArg(fooArg), "bar" })); + Assert.assertEquals(2, ArgTransformer.runnableExecuted); + } + + @Test + public void testInstanceTransformingInvokableMethod() { + InstanceTransformer.runnableExecuted = 0; + + //test is intentionally *NOT* using the bean, but instead passes in new instance every time + String fooArg = "fooArg"; + String expected = (fooArg + fooArg).toUpperCase(); + TransformableBean targetInstance; + + // transformer defined on other class + targetInstance = new TransformableBean(); + Assert.assertFalse(targetInstance.isTransformed()); + Assert.assertEquals(expected, + extension.getInstanceTransformerInvoker().invoke(targetInstance, new Object[] { new FooArg(fooArg), fooArg })); + Assert.assertTrue(targetInstance.isTransformed()); + + // transformer defined on other class with Consumer param + targetInstance = new TransformableBean(); + Assert.assertFalse(targetInstance.isTransformed()); + Assert.assertEquals(0, InstanceTransformer.runnableExecuted); + Assert.assertEquals(expected, extension.getInstanceTransformerWithConsumerInvoker().invoke(targetInstance, + new Object[] { new FooArg(fooArg), fooArg })); + Assert.assertEquals(1, InstanceTransformer.runnableExecuted); + Assert.assertTrue(targetInstance.isTransformed()); + + // transformer on the bean class, no arg method + targetInstance = new TransformableBean(); + Assert.assertFalse(targetInstance.isTransformed()); + Assert.assertEquals(expected, extension.getInstanceTransformerNoParamInvoker().invoke(targetInstance, + new Object[] { new FooArg(fooArg), fooArg })); + Assert.assertTrue(targetInstance.isTransformed()); + } + + @Test + public void testReturnValueTransformingInvokableMethod() { + String fooArg = " fooArg "; + String expected = (fooArg + fooArg).strip(); + + // transformer defined on other class + Assert.assertEquals(expected, + extension.getReturnTransformerInvoker().invoke(transformableBean, new Object[] { new FooArg(fooArg), fooArg })); + Assert.assertEquals(expected, extension.getStaticReturnTransformerInvoker().invoke(transformableBean, + new Object[] { new FooArg(fooArg), fooArg })); + + // transformer on the result class (String), no arg method + Assert.assertEquals(expected, extension.getReturnTransformerNoParamInvoker().invoke(transformableBean, + new Object[] { new FooArg(fooArg), fooArg })); + Assert.assertEquals(expected, extension.getStaticReturnTransformerNoParamInvoker().invoke(transformableBean, + new Object[] { new FooArg(fooArg), fooArg })); + } + + @Test + public void testExceptionTransformingInvokableMethod() { + String expected = IllegalArgumentException.class.getSimpleName(); + String expectedStatic = IllegalStateException.class.getSimpleName(); + + // exception transformer can only be defined on other class + Assert.assertEquals(expected, + extension.getExceptionTransformerInvoker().invoke(trulyExceptionalBean, new Object[] { "foo", 42 })); + Assert.assertEquals(expectedStatic, + extension.getStaticExceptionTransformerInvoker().invoke(trulyExceptionalBean, new Object[] { "foo", 42 })); + } + + @Test + public void testInvocationWrapperInvokableMethod() { + String expected = "foo42foo42"; + + // invocation wrapper defined on other class + Assert.assertEquals(expected, extension.getInvocationWrapperInvoker().invoke(simpleBean, new Object[] { "foo", 42 })); + Assert.assertEquals(expected, + extension.getStaticInvocationWrapperInvoker().invoke(simpleBean, new Object[] { "foo", 42 })); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/ObservingExtension.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/ObservingExtension.java new file mode 100644 index 0000000000..7922574ac2 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/ObservingExtension.java @@ -0,0 +1,272 @@ +package org.jboss.weld.tests.invokable; + +import java.util.Collection; + +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.spi.AnnotatedMethod; +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.inject.spi.ProcessManagedBean; +import jakarta.enterprise.invoke.Invoker; + +import org.jboss.weld.tests.invokable.common.ArgTransformer; +import org.jboss.weld.tests.invokable.common.ExceptionTransformer; +import org.jboss.weld.tests.invokable.common.FooArg; +import org.jboss.weld.tests.invokable.common.InstanceTransformer; +import org.jboss.weld.tests.invokable.common.InvocationWrapper; +import org.jboss.weld.tests.invokable.common.ReturnValueTransformer; +import org.jboss.weld.tests.invokable.common.SimpleBean; +import org.jboss.weld.tests.invokable.common.TransformableBean; +import org.jboss.weld.tests.invokable.common.TrulyExceptionalBean; +import org.junit.Assert; + +public class ObservingExtension implements Extension { + + public Invoker getNoTransformationInvoker() { + return noTransformationInvoker; + } + + public Invoker getInstanceLookupInvoker() { + return instanceLookupInvoker; + } + + public Invoker getArgLookupInvoker() { + return argLookupInvoker; + } + + public Invoker getLookupAllInvoker() { + return lookupAllInvoker; + } + + public Invoker getStaticNoTransformationInvoker() { + return staticNoTransformationInvoker; + } + + public Invoker getStaticInstanceLookupInvoker() { + return staticInstanceLookupInvoker; + } + + public Invoker getStaticArgLookupInvoker() { + return staticArgLookupInvoker; + } + + public Invoker getStaticLookupAllInvoker() { + return staticLookupAllInvoker; + } + + public Invoker getArgTransformingInvoker() { + return argTransformingInvoker; + } + + public Invoker getStaticArgTransformingInvoker() { + return staticArgTransformingInvoker; + } + + public Invoker getArgTransformerWithConsumerInvoker() { + return argTransformerWithConsumerInvoker; + } + + public Invoker getStaticArgTransformerWithConsumerInvoker() { + return staticArgTransformerWithConsumerInvoker; + } + + public Invoker getInstanceTransformerInvoker() { + return instanceTransformerInvoker; + } + + public Invoker getInstanceTransformerWithConsumerInvoker() { + return instanceTransformerWithConsumerInvoker; + } + + public Invoker getInstanceTransformerNoParamInvoker() { + return instanceTransformerNoParamInvoker; + } + + public Invoker getReturnTransformerInvoker() { + return returnTransformerInvoker; + } + + public Invoker getStaticReturnTransformerInvoker() { + return staticReturnTransformerInvoker; + } + + public Invoker getReturnTransformerNoParamInvoker() { + return returnTransformerNoParamInvoker; + } + + public Invoker getStaticReturnTransformerNoParamInvoker() { + return staticReturnTransformerNoParamInvoker; + } + + public Invoker getExceptionTransformerInvoker() { + return exceptionTransformerInvoker; + } + + public Invoker getStaticExceptionTransformerInvoker() { + return staticExceptionTransformerInvoker; + } + + public Invoker getInvocationWrapperInvoker() { + return invocationWrapperInvoker; + } + + public Invoker getStaticInvocationWrapperInvoker() { + return staticInvocationWrapperInvoker; + } + + // basic invokers, some with lookup + private Invoker noTransformationInvoker; + private Invoker instanceLookupInvoker; + private Invoker argLookupInvoker; + private Invoker lookupAllInvoker; + private Invoker staticNoTransformationInvoker; + private Invoker staticInstanceLookupInvoker; + private Invoker staticArgLookupInvoker; + private Invoker staticLookupAllInvoker; + + // method arg transformers + private Invoker argTransformingInvoker; + private Invoker staticArgTransformingInvoker; + private Invoker argTransformerWithConsumerInvoker; + private Invoker staticArgTransformerWithConsumerInvoker; + + // instance transformers + private Invoker instanceTransformerInvoker; + private Invoker instanceTransformerWithConsumerInvoker; + private Invoker instanceTransformerNoParamInvoker; + + // return value transformers + private Invoker returnTransformerInvoker; + private Invoker returnTransformerNoParamInvoker; + private Invoker staticReturnTransformerInvoker; + private Invoker staticReturnTransformerNoParamInvoker; + + // exception transformers + private Invoker exceptionTransformerInvoker; + private Invoker staticExceptionTransformerInvoker; + + // invocation wrapper + private Invoker invocationWrapperInvoker; + private Invoker staticInvocationWrapperInvoker; + + public void createNoTransformationInvokers(@Observes ProcessManagedBean pmb) { + Collection> invokableMethods = pmb.getAnnotatedBeanClass().getMethods(); + Assert.assertEquals(4, invokableMethods.size()); + for (AnnotatedMethod invokableMethod : invokableMethods) { + if (invokableMethod.getJavaMember().getName().contains("staticPing")) { + staticNoTransformationInvoker = pmb.createInvoker(invokableMethod).build(); + staticInstanceLookupInvoker = pmb.createInvoker(invokableMethod).setInstanceLookup().build(); + staticArgLookupInvoker = pmb.createInvoker(invokableMethod).setArgumentLookup(0).setArgumentLookup(1).build(); + staticLookupAllInvoker = pmb.createInvoker(invokableMethod).setArgumentLookup(0).setArgumentLookup(1) + .setInstanceLookup().build(); + } else if (invokableMethod.getJavaMember().getName().contains("ping")) { + noTransformationInvoker = pmb.createInvoker(invokableMethod).build(); + instanceLookupInvoker = pmb.createInvoker(invokableMethod).setInstanceLookup().build(); + argLookupInvoker = pmb.createInvoker(invokableMethod).setArgumentLookup(0).setArgumentLookup(1).build(); + lookupAllInvoker = pmb.createInvoker(invokableMethod).setArgumentLookup(0).setArgumentLookup(1) + .setInstanceLookup().build(); + } + } + } + + public void createArgTransformationInvokers(@Observes ProcessManagedBean pmb) { + Collection> invokableMethods = pmb.getAnnotatedBeanClass().getMethods(); + Assert.assertEquals(4, invokableMethods.size()); + for (AnnotatedMethod invokableMethod : invokableMethods) { + if (invokableMethod.getJavaMember().getName().contains("staticPing")) { + staticArgTransformingInvoker = pmb.createInvoker(invokableMethod) + .setArgumentTransformer(0, FooArg.class, "doubleTheString") // non-static transformer method + .setArgumentTransformer(1, ArgTransformer.class, "transform") // static transformer method + .build(); + staticArgTransformerWithConsumerInvoker = pmb.createInvoker(invokableMethod) + .setArgumentTransformer(0, FooArg.class, "doubleTheString") // non-static transformer method + .setArgumentTransformer(1, ArgTransformer.class, "transform2") // static transformer method with Consumer + .build(); + } else if (invokableMethod.getJavaMember().getName().contains("ping")) { + argTransformingInvoker = pmb.createInvoker(invokableMethod) + .setArgumentTransformer(0, FooArg.class, "doubleTheString") // non-static transformer method + .setArgumentTransformer(1, ArgTransformer.class, "transform") // static transformer method + .build(); + argTransformerWithConsumerInvoker = pmb.createInvoker(invokableMethod) + .setArgumentTransformer(0, FooArg.class, "doubleTheString") // non-static transformer method + .setArgumentTransformer(1, ArgTransformer.class, "transform2") // static transformer method with Consumer + .build(); + } + } + } + + public void createInstanceTransformationInvokers(@Observes ProcessManagedBean pmb) { + Collection> invokableMethods = pmb.getAnnotatedBeanClass().getMethods(); + Assert.assertEquals(4, invokableMethods.size()); + for (AnnotatedMethod invokableMethod : invokableMethods) { + if (invokableMethod.getJavaMember().getName().contains("ping")) { + instanceTransformerInvoker = pmb.createInvoker(invokableMethod) + .setInstanceTransformer(InstanceTransformer.class, "transform") + .build(); + instanceTransformerWithConsumerInvoker = pmb.createInvoker(invokableMethod) + .setInstanceTransformer(InstanceTransformer.class, "transform2") + .build(); + instanceTransformerNoParamInvoker = pmb.createInvoker(invokableMethod) + .setInstanceTransformer(TransformableBean.class, "setTransformed") + .build(); + } + } + } + + public void createReturnValueTransformationInvokers(@Observes ProcessManagedBean pmb) { + Collection> invokableMethods = pmb.getAnnotatedBeanClass().getMethods(); + Assert.assertEquals(4, invokableMethods.size()); + for (AnnotatedMethod invokableMethod : invokableMethods) { + if (invokableMethod.getJavaMember().getName().contains("ping")) { + returnTransformerInvoker = pmb.createInvoker(invokableMethod) + .setReturnValueTransformer(ReturnValueTransformer.class, "transform") + .build(); + returnTransformerNoParamInvoker = pmb.createInvoker(invokableMethod) + .setReturnValueTransformer(String.class, "strip") + .build(); + + } else if (invokableMethod.getJavaMember().getName().contains("staticPing")) { + staticReturnTransformerInvoker = pmb.createInvoker(invokableMethod) + .setReturnValueTransformer(ReturnValueTransformer.class, "transform") + .build(); + staticReturnTransformerNoParamInvoker = pmb.createInvoker(invokableMethod) + .setReturnValueTransformer(String.class, "strip") + .build(); + } + } + } + + public void createExceptionTransformationInvokers(@Observes ProcessManagedBean pmb) { + Collection> invokableMethods = pmb.getAnnotatedBeanClass().getMethods(); + Assert.assertEquals(2, invokableMethods.size()); + for (AnnotatedMethod invokableMethod : invokableMethods) { + if (invokableMethod.getJavaMember().getName().contains("ping")) { + exceptionTransformerInvoker = pmb.createInvoker(invokableMethod) + .setExceptionTransformer(ExceptionTransformer.class, "transform") + .build(); + + } else if (invokableMethod.getJavaMember().getName().contains("staticPing")) { + staticExceptionTransformerInvoker = pmb.createInvoker(invokableMethod) + .setExceptionTransformer(ExceptionTransformer.class, "transform") + .build(); + } + } + } + + public void createInvocationWrapperInvokers(@Observes ProcessManagedBean pmb) { + Collection> invokableMethods = pmb.getAnnotatedBeanClass().getMethods(); + Assert.assertEquals(4, invokableMethods.size()); + for (AnnotatedMethod invokableMethod : invokableMethods) { + if (invokableMethod.getJavaMember().getName().contains("ping")) { + invocationWrapperInvoker = pmb.createInvoker(invokableMethod) + .setInvocationWrapper(InvocationWrapper.class, "transform") + .build(); + + } else if (invokableMethod.getJavaMember().getName().contains("staticPing")) { + staticInvocationWrapperInvoker = pmb.createInvoker(invokableMethod) + .setInvocationWrapper(InvocationWrapper.class, "transform") + .build(); + } + } + } + +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/SynthBean.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/SynthBean.java new file mode 100644 index 0000000000..21261198df --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/SynthBean.java @@ -0,0 +1,233 @@ +package org.jboss.weld.tests.invokable; + +import jakarta.enterprise.invoke.Invoker; + +import org.jboss.weld.tests.invokable.common.SimpleBean; +import org.jboss.weld.tests.invokable.common.TransformableBean; +import org.jboss.weld.tests.invokable.common.TrulyExceptionalBean; + +/** + * Bean registered via BCE + */ +public class SynthBean { + public Invoker getNoTransformationInvoker() { + return noTransformationInvoker; + } + + public Invoker getInstanceLookupInvoker() { + return instanceLookupInvoker; + } + + public Invoker getArgLookupInvoker() { + return argLookupInvoker; + } + + public Invoker getLookupAllInvoker() { + return lookupAllInvoker; + } + + public Invoker getStaticNoTransformationInvoker() { + return staticNoTransformationInvoker; + } + + public Invoker getStaticInstanceLookupInvoker() { + return staticInstanceLookupInvoker; + } + + public Invoker getStaticArgLookupInvoker() { + return staticArgLookupInvoker; + } + + public Invoker getStaticLookupAllInvoker() { + return staticLookupAllInvoker; + } + + public Invoker getArgTransformingInvoker() { + return argTransformingInvoker; + } + + public Invoker getStaticArgTransformingInvoker() { + return staticArgTransformingInvoker; + } + + public Invoker getArgTransformerWithConsumerInvoker() { + return argTransformerWithConsumerInvoker; + } + + public Invoker getStaticArgTransformerWithConsumerInvoker() { + return staticArgTransformerWithConsumerInvoker; + } + + public Invoker getInstanceTransformerInvoker() { + return instanceTransformerInvoker; + } + + public Invoker getInstanceTransformerWithConsumerInvoker() { + return instanceTransformerWithConsumerInvoker; + } + + public void setNoTransformationInvoker(Invoker noTransformationInvoker) { + this.noTransformationInvoker = noTransformationInvoker; + } + + public void setInstanceLookupInvoker(Invoker instanceLookupInvoker) { + this.instanceLookupInvoker = instanceLookupInvoker; + } + + public void setArgLookupInvoker(Invoker argLookupInvoker) { + this.argLookupInvoker = argLookupInvoker; + } + + public void setLookupAllInvoker(Invoker lookupAllInvoker) { + this.lookupAllInvoker = lookupAllInvoker; + } + + public void setStaticNoTransformationInvoker(Invoker staticNoTransformationInvoker) { + this.staticNoTransformationInvoker = staticNoTransformationInvoker; + } + + public void setStaticInstanceLookupInvoker(Invoker staticInstanceLookupInvoker) { + this.staticInstanceLookupInvoker = staticInstanceLookupInvoker; + } + + public void setStaticArgLookupInvoker(Invoker staticArgLookupInvoker) { + this.staticArgLookupInvoker = staticArgLookupInvoker; + } + + public void setStaticLookupAllInvoker(Invoker staticLookupAllInvoker) { + this.staticLookupAllInvoker = staticLookupAllInvoker; + } + + public void setArgTransformingInvoker(Invoker argTransformingInvoker) { + this.argTransformingInvoker = argTransformingInvoker; + } + + public void setStaticArgTransformingInvoker(Invoker staticArgTransformingInvoker) { + this.staticArgTransformingInvoker = staticArgTransformingInvoker; + } + + public void setArgTransformerWithConsumerInvoker(Invoker argTransformerWithConsumerInvoker) { + this.argTransformerWithConsumerInvoker = argTransformerWithConsumerInvoker; + } + + public void setStaticArgTransformerWithConsumerInvoker( + Invoker staticArgTransformerWithConsumerInvoker) { + this.staticArgTransformerWithConsumerInvoker = staticArgTransformerWithConsumerInvoker; + } + + public void setInstanceTransformerInvoker(Invoker instanceTransformerInvoker) { + this.instanceTransformerInvoker = instanceTransformerInvoker; + } + + public void setInstanceTransformerWithConsumerInvoker( + Invoker instanceTransformerWithConsumerInvoker) { + this.instanceTransformerWithConsumerInvoker = instanceTransformerWithConsumerInvoker; + } + + public void setInstanceTransformerNoParamInvoker(Invoker instanceTransformerNoParamInvoker) { + this.instanceTransformerNoParamInvoker = instanceTransformerNoParamInvoker; + } + + public void setReturnTransformerInvoker(Invoker returnTransformerInvoker) { + this.returnTransformerInvoker = returnTransformerInvoker; + } + + public void setReturnTransformerNoParamInvoker(Invoker returnTransformerNoParamInvoker) { + this.returnTransformerNoParamInvoker = returnTransformerNoParamInvoker; + } + + public void setStaticReturnTransformerInvoker(Invoker staticReturnTransformerInvoker) { + this.staticReturnTransformerInvoker = staticReturnTransformerInvoker; + } + + public void setStaticReturnTransformerNoParamInvoker(Invoker staticReturnTransformerNoParamInvoker) { + this.staticReturnTransformerNoParamInvoker = staticReturnTransformerNoParamInvoker; + } + + public void setExceptionTransformerInvoker(Invoker exceptionTransformerInvoker) { + this.exceptionTransformerInvoker = exceptionTransformerInvoker; + } + + public void setStaticExceptionTransformerInvoker(Invoker staticExceptionTransformerInvoker) { + this.staticExceptionTransformerInvoker = staticExceptionTransformerInvoker; + } + + public void setInvocationWrapperInvoker(Invoker invocationWrapperInvoker) { + this.invocationWrapperInvoker = invocationWrapperInvoker; + } + + public void setStaticInvocationWrapperInvoker(Invoker staticInvocationWrapperInvoker) { + this.staticInvocationWrapperInvoker = staticInvocationWrapperInvoker; + } + + public Invoker getInstanceTransformerNoParamInvoker() { + return instanceTransformerNoParamInvoker; + } + + public Invoker getReturnTransformerInvoker() { + return returnTransformerInvoker; + } + + public Invoker getStaticReturnTransformerInvoker() { + return staticReturnTransformerInvoker; + } + + public Invoker getReturnTransformerNoParamInvoker() { + return returnTransformerNoParamInvoker; + } + + public Invoker getStaticReturnTransformerNoParamInvoker() { + return staticReturnTransformerNoParamInvoker; + } + + public Invoker getExceptionTransformerInvoker() { + return exceptionTransformerInvoker; + } + + public Invoker getStaticExceptionTransformerInvoker() { + return staticExceptionTransformerInvoker; + } + + public Invoker getInvocationWrapperInvoker() { + return invocationWrapperInvoker; + } + + public Invoker getStaticInvocationWrapperInvoker() { + return staticInvocationWrapperInvoker; + } + + // basic invokers, some with lookup + private Invoker noTransformationInvoker; + private Invoker instanceLookupInvoker; + private Invoker argLookupInvoker; + private Invoker lookupAllInvoker; + private Invoker staticNoTransformationInvoker; + private Invoker staticInstanceLookupInvoker; + private Invoker staticArgLookupInvoker; + private Invoker staticLookupAllInvoker; + + // method arg transformers + private Invoker argTransformingInvoker; + private Invoker staticArgTransformingInvoker; + private Invoker argTransformerWithConsumerInvoker; + private Invoker staticArgTransformerWithConsumerInvoker; + + // instance transformers + private Invoker instanceTransformerInvoker; + private Invoker instanceTransformerWithConsumerInvoker; + private Invoker instanceTransformerNoParamInvoker; + + // return value transformers + private Invoker returnTransformerInvoker; + private Invoker returnTransformerNoParamInvoker; + private Invoker staticReturnTransformerInvoker; + private Invoker staticReturnTransformerNoParamInvoker; + + // exception transformers + private Invoker exceptionTransformerInvoker; + private Invoker staticExceptionTransformerInvoker; + + // invocation wrapper + private Invoker invocationWrapperInvoker; + private Invoker staticInvocationWrapperInvoker; +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/ArgTransformer.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/ArgTransformer.java new file mode 100644 index 0000000000..3ec347e7ed --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/ArgTransformer.java @@ -0,0 +1,19 @@ +package org.jboss.weld.tests.invokable.common; + +import java.util.function.Consumer; + +// non-bean class intentionally +public class ArgTransformer { + + public static String transformed = "TRANSFORMED"; + public static int runnableExecuted = 0; + + public static String transform(String s) { + return transformed; + } + + public static String transform2(String s, Consumer runnableConsumer) { + runnableConsumer.accept(() -> runnableExecuted++); + return transformed; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/ExceptionTransformer.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/ExceptionTransformer.java new file mode 100644 index 0000000000..5a3b0107d1 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/ExceptionTransformer.java @@ -0,0 +1,14 @@ +package org.jboss.weld.tests.invokable.common; + +// non-bean intentionally +public class ExceptionTransformer { + + public static String transform(Throwable t) { + if (t instanceof IllegalArgumentException) { + return IllegalArgumentException.class.getSimpleName(); + } else if (t instanceof IllegalStateException) { + return IllegalStateException.class.getSimpleName(); + } + return ExceptionTransformer.class.getSimpleName(); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/FooArg.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/FooArg.java new file mode 100644 index 0000000000..5f57271b5d --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/FooArg.java @@ -0,0 +1,20 @@ +package org.jboss.weld.tests.invokable.common; + +// servers as transformable arg of a bean method +public class FooArg { + + private String s; + + public FooArg(String s) { + this.s = s; + } + + public FooArg doubleTheString() { + this.s = s + s; + return this; + } + + public String getString() { + return s; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/HelperBean.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/HelperBean.java new file mode 100644 index 0000000000..0588f8986f --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/HelperBean.java @@ -0,0 +1,35 @@ +package org.jboss.weld.tests.invokable.common; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Disposes; +import jakarta.enterprise.inject.Produces; + +@ApplicationScoped +public class HelperBean { + + public static int timesStringDestroyed = 0; + public static int timesIntDestroyed = 0; + + @Produces + public String produceString() { + return "bar"; + } + + @Produces + public int produceInt() { + return 42; + } + + public void stringDisposer(@Disposes String s) { + timesStringDestroyed++; + } + + public void intDisposer(@Disposes int i) { + timesIntDestroyed++; + } + + public static void clearDestroyedCounters() { + timesIntDestroyed = 0; + timesStringDestroyed = 0; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/InstanceTransformer.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/InstanceTransformer.java new file mode 100644 index 0000000000..59a8cee3ba --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/InstanceTransformer.java @@ -0,0 +1,20 @@ +package org.jboss.weld.tests.invokable.common; + +import java.util.function.Consumer; + +// non-bean intentionally +public class InstanceTransformer { + + public static int runnableExecuted = 0; + + public static TransformableBean transform(TransformableBean bean) { + bean.setTransformed(); + return bean; + } + + public static TransformableBean transform2(TransformableBean bean, Consumer consumer) { + consumer.accept(() -> runnableExecuted++); + bean.setTransformed(); + return bean; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/InvocationWrapper.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/InvocationWrapper.java new file mode 100644 index 0000000000..0d55703200 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/InvocationWrapper.java @@ -0,0 +1,16 @@ +package org.jboss.weld.tests.invokable.common; + +import jakarta.enterprise.invoke.Invoker; + +public class InvocationWrapper { + public static Object transform(SimpleBean instance, Object[] arguments, Invoker invoker) { + // perform repeated invocation; just to verify the invoker is stateless + Object result1 = invoker.invoke(instance, arguments); + Object result2 = invoker.invoke(instance, arguments); + if (result1 instanceof String && result2 instanceof String) { + return (String) result1 + result2; + } else { + throw new AssertionError(); + } + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/ReturnValueTransformer.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/ReturnValueTransformer.java new file mode 100644 index 0000000000..0f1ea2516d --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/ReturnValueTransformer.java @@ -0,0 +1,9 @@ +package org.jboss.weld.tests.invokable.common; + +// non-bean class intentionally +public class ReturnValueTransformer { + + static public String transform(String returnValue) { + return returnValue.strip(); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/SimpleBean.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/SimpleBean.java new file mode 100644 index 0000000000..694d50fec1 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/SimpleBean.java @@ -0,0 +1,28 @@ +package org.jboss.weld.tests.invokable.common; + +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.Dependent; + +@Dependent +public class SimpleBean { + + public static int preDestroyInvoked = 0; + + public String ping(String s, int i) { + return s + i; + } + + public static String staticPing(String s, int i) { + return s + i; + } + + @PreDestroy + public void destroy() { + preDestroyInvoked++; + } + + public static void resetDestroyCounter() { + preDestroyInvoked = 0; + } + +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/TransformableBean.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/TransformableBean.java new file mode 100644 index 0000000000..9de0f8d416 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/TransformableBean.java @@ -0,0 +1,32 @@ +package org.jboss.weld.tests.invokable.common; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class TransformableBean { + + // used with instance transformer + private boolean transformed = false; + + public TransformableBean setTransformed() { + this.transformed = true; + return this; + } + + public boolean isTransformed() { + return transformed; + } + + public String ping(FooArg foo, String s) { + String result = foo.getString() + s; + if (transformed) { + return result.toUpperCase(); + } else { + return result; + } + } + + public static String staticPing(FooArg foo, String s) { + return foo.getString() + s; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/TrulyExceptionalBean.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/TrulyExceptionalBean.java new file mode 100644 index 0000000000..47c9028ce9 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/common/TrulyExceptionalBean.java @@ -0,0 +1,15 @@ +package org.jboss.weld.tests.invokable.common; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class TrulyExceptionalBean { + + public String ping(String something, int somethingElse) { + throw new IllegalArgumentException("intended"); + } + + public static String staticPing(String something, int somethingElse) { + throw new IllegalStateException("intended"); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/BeanProducer.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/BeanProducer.java new file mode 100644 index 0000000000..19ad91895c --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/BeanProducer.java @@ -0,0 +1,44 @@ +package org.jboss.weld.tests.invokable.lookup; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +@ApplicationScoped +public class BeanProducer { + + @Produces + @MyQualifier1("foo") + @MyQualifier4("binding") + public String produce1() { + return MyQualifier1.class.getSimpleName() + MyQualifier4.class.getSimpleName(); + } + + @Produces + @MyQualifier2 + public String produce2() { + return MyQualifier2.class.getSimpleName(); + } + + @Produces + @MyQualifier5 + public String produceAmbig1() { + throw new IllegalStateException("Ambiguous producer should never be invoked"); + } + + @Produces + @MyQualifier5 + public String produceAmbig2() { + throw new IllegalStateException("Ambiguous producer should never be invoked"); + } + + @Produces + public String producePlain() { + throw new IllegalStateException("No qualifier producer should never be invoked"); + } + + @Produces + @ToBeQualifier + public String produceQualified() { + return ToBeQualifier.class.getSimpleName(); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/InvokableBean.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/InvokableBean.java new file mode 100644 index 0000000000..54db3689d3 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/InvokableBean.java @@ -0,0 +1,30 @@ +package org.jboss.weld.tests.invokable.lookup; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +@MyQualifier1("myBean") +public class InvokableBean { + + public String instanceLookup() { + return InvokableBean.class.getSimpleName(); + } + + // there are two producers providing a bean for the first argument + public String ambiguousLookup(@MyQualifier5 String a) { + return a; + } + + // there is no bean with @MyQualifier3 + public String unsatisfiedLookup(@MyQualifier3 String a) { + return a; + } + + public String correctLookup(@MyQualifier1("noMatter") @MyQualifier4("binding") String a, @MyQualifier2 String b) { + return a + b; + } + + public String lookupWithRegisteredQualifier(@ToBeQualifier String a) { + return a; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/InvokableMethodLookupTest.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/InvokableMethodLookupTest.java new file mode 100644 index 0000000000..c92885836c --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/InvokableMethodLookupTest.java @@ -0,0 +1,80 @@ +package org.jboss.weld.tests.invokable.lookup; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import jakarta.enterprise.inject.AmbiguousResolutionException; +import jakarta.enterprise.inject.UnsatisfiedResolutionException; +import jakarta.enterprise.inject.spi.Extension; +import jakarta.inject.Inject; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.BeanArchive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.weld.test.util.Utils; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(Arquillian.class) +public class InvokableMethodLookupTest { + + @Deployment + public static Archive getDeployment() { + return ShrinkWrap.create(BeanArchive.class, Utils.getDeploymentNameAsHash(InvokableMethodLookupTest.class)) + .addPackage(InvokableMethodLookupTest.class.getPackage()) + .addAsServiceProvider(Extension.class, InvokerRegistreringExtension.class); + } + + @Inject + InvokerRegistreringExtension extension; + + @Inject + @MyQualifier1("abc") + InvokableBean bean; + + @Test + public void testInstanceLookupWithQualifiers() { + Object invokerResult = extension.getInstanceLookupInvoker().invoke(null, new Object[] {}); + assertTrue(invokerResult instanceof String); + assertEquals(InvokableBean.class.getSimpleName(), invokerResult); + } + + @Test + public void testCorrectArgLookupWithQualifiers() { + Object invokerResult = extension.getCorrectLookupInvoker().invoke(bean, new Object[] { null, null }); + assertTrue(invokerResult instanceof String); + assertEquals( + MyQualifier1.class.getSimpleName() + MyQualifier4.class.getSimpleName() + MyQualifier2.class.getSimpleName(), + invokerResult); + } + + @Test + public void testLookupWithRegisteredQualifier() { + Object invokerResult = extension.getLookupWithRegisteredQualifier().invoke(bean, new Object[] { null }); + assertTrue(invokerResult instanceof String); + assertEquals(ToBeQualifier.class.getSimpleName(), invokerResult); + } + + @Test + public void testUnsatisfiedLookupWithQualifier() { + try { + Object invokerResult = extension.getUnsatisfiedLookupInvoker().invoke(bean, new Object[] { null }); + fail(); + } catch (UnsatisfiedResolutionException e) { + // expected + } + } + + @Test + public void testAmbigLookupWithQualifiers() { + try { + Object invokerResult = extension.getAmbiguousLookupInvoker().invoke(bean, new Object[] { null }); + fail(); + } catch (AmbiguousResolutionException e) { + // expected + } + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/InvokerRegistreringExtension.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/InvokerRegistreringExtension.java new file mode 100644 index 0000000000..49eb46f3b5 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/InvokerRegistreringExtension.java @@ -0,0 +1,63 @@ +package org.jboss.weld.tests.invokable.lookup; + +import java.util.Collection; + +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.spi.AnnotatedMethod; +import jakarta.enterprise.inject.spi.BeforeBeanDiscovery; +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.inject.spi.ProcessManagedBean; +import jakarta.enterprise.invoke.Invoker; + +import org.junit.Assert; + +public class InvokerRegistreringExtension implements Extension { + + private Invoker instanceLookupInvoker; + private Invoker unsatisfiedLookupInvoker; + private Invoker ambiguousLookupInvoker; + private Invoker correctLookupInvoker; + private Invoker lookupWithRegisteredQualifier; + + public Invoker getInstanceLookupInvoker() { + return instanceLookupInvoker; + } + + public Invoker getUnsatisfiedLookupInvoker() { + return unsatisfiedLookupInvoker; + } + + public Invoker getAmbiguousLookupInvoker() { + return ambiguousLookupInvoker; + } + + public Invoker getCorrectLookupInvoker() { + return correctLookupInvoker; + } + + public Invoker getLookupWithRegisteredQualifier() { + return lookupWithRegisteredQualifier; + } + + public void createInvokers(@Observes ProcessManagedBean pmb) { + Collection> invokableMethods = pmb.getAnnotatedBeanClass().getMethods(); + Assert.assertEquals(5, invokableMethods.size()); + for (AnnotatedMethod invokableMethod : invokableMethods) { + if (invokableMethod.getJavaMember().getName().contains("instanceLookup")) { + instanceLookupInvoker = pmb.createInvoker(invokableMethod).setInstanceLookup().build(); + } else if (invokableMethod.getJavaMember().getName().contains("unsatisfiedLookup")) { + unsatisfiedLookupInvoker = pmb.createInvoker(invokableMethod).setArgumentLookup(0).build(); + } else if (invokableMethod.getJavaMember().getName().contains("ambiguousLookup")) { + ambiguousLookupInvoker = pmb.createInvoker(invokableMethod).setArgumentLookup(0).build(); + } else if (invokableMethod.getJavaMember().getName().contains("lookupWithRegisteredQualifier")) { + lookupWithRegisteredQualifier = pmb.createInvoker(invokableMethod).setArgumentLookup(0).build(); + } else { + correctLookupInvoker = pmb.createInvoker(invokableMethod).setArgumentLookup(0).setArgumentLookup(1).build(); + } + } + } + + public void registerQualifier(@Observes BeforeBeanDiscovery bbd) { + bbd.addQualifier(ToBeQualifier.class); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier1.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier1.java new file mode 100644 index 0000000000..25ca54a356 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier1.java @@ -0,0 +1,16 @@ +package org.jboss.weld.tests.invokable.lookup; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import jakarta.enterprise.util.Nonbinding; +import jakarta.inject.Qualifier; + +@Retention(RetentionPolicy.RUNTIME) +@Qualifier +public @interface MyQualifier1 { + + // non-binding + @Nonbinding + String value(); +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier2.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier2.java new file mode 100644 index 0000000000..e62af269d7 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier2.java @@ -0,0 +1,11 @@ +package org.jboss.weld.tests.invokable.lookup; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import jakarta.inject.Qualifier; + +@Retention(RetentionPolicy.RUNTIME) +@Qualifier +public @interface MyQualifier2 { +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier3.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier3.java new file mode 100644 index 0000000000..494aeb9135 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier3.java @@ -0,0 +1,12 @@ +package org.jboss.weld.tests.invokable.lookup; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import jakarta.inject.Qualifier; + +// no bean with this qualifier is supposed to exist +@Retention(RetentionPolicy.RUNTIME) +@Qualifier +public @interface MyQualifier3 { +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier4.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier4.java new file mode 100644 index 0000000000..643134c741 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier4.java @@ -0,0 +1,14 @@ +package org.jboss.weld.tests.invokable.lookup; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import jakarta.inject.Qualifier; + +@Retention(RetentionPolicy.RUNTIME) +@Qualifier +public @interface MyQualifier4 { + + // binding value + String value(); +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier5.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier5.java new file mode 100644 index 0000000000..e81d0f8c18 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/MyQualifier5.java @@ -0,0 +1,11 @@ +package org.jboss.weld.tests.invokable.lookup; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import jakarta.inject.Qualifier; + +@Retention(RetentionPolicy.RUNTIME) +@Qualifier +public @interface MyQualifier5 { +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/NotAQualifier.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/NotAQualifier.java new file mode 100644 index 0000000000..11a9a51c08 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/NotAQualifier.java @@ -0,0 +1,9 @@ +package org.jboss.weld.tests.invokable.lookup; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +// deliberately not a qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface NotAQualifier { +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/ToBeQualifier.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/ToBeQualifier.java new file mode 100644 index 0000000000..bae963e91e --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/lookup/ToBeQualifier.java @@ -0,0 +1,9 @@ +package org.jboss.weld.tests.invokable.lookup; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +// an extension turns this annotation into a CDI qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface ToBeQualifier { +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/ActualBean.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/ActualBean.java new file mode 100644 index 0000000000..aff266b255 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/ActualBean.java @@ -0,0 +1,25 @@ +package org.jboss.weld.tests.invokable.transformers.input; + +import jakarta.enterprise.context.Dependent; + +@Dependent +public class ActualBean { + + private Integer number; + + public ActualBean() { + this(0); + } + + public ActualBean(Integer i) { + this.number = i; + } + + public Integer getNumber() { + return number; + } + + public Beta ping(Number n) { + return new Gamma(n, number); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/Alpha.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/Alpha.java new file mode 100644 index 0000000000..4da58fc287 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/Alpha.java @@ -0,0 +1,14 @@ +package org.jboss.weld.tests.invokable.transformers.input; + +public class Alpha { + + protected Number n; + + public Alpha() { + this.n = 0; + } + + public String ping() { + return n.toString(); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/AugmentedBean.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/AugmentedBean.java new file mode 100644 index 0000000000..d41679aea4 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/AugmentedBean.java @@ -0,0 +1,7 @@ +package org.jboss.weld.tests.invokable.transformers.input; + +public class AugmentedBean extends ActualBean { + public AugmentedBean(Integer i) { + super(i); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/Beta.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/Beta.java new file mode 100644 index 0000000000..83a25f6d1a --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/Beta.java @@ -0,0 +1,20 @@ +package org.jboss.weld.tests.invokable.transformers.input; + +public class Beta extends Alpha { + + protected Integer i; + + public Beta() { + super(); + this.i = 0; + } + + public Beta(Number n, Integer i) { + this.n = n; + this.i = i; + } + + public Integer getInteger() { + return i; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/Gamma.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/Gamma.java new file mode 100644 index 0000000000..9073bf5d65 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/Gamma.java @@ -0,0 +1,11 @@ +package org.jboss.weld.tests.invokable.transformers.input; + +public class Gamma extends Beta { + public Gamma(Number n, Integer i) { + super(n, i); + } + + public Gamma() { + super(); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/InputTransformerTest.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/InputTransformerTest.java new file mode 100644 index 0000000000..2cd4b6fc77 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/InputTransformerTest.java @@ -0,0 +1,65 @@ +package org.jboss.weld.tests.invokable.transformers.input; + +import jakarta.enterprise.inject.spi.Extension; +import jakarta.inject.Inject; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.BeanArchive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.weld.test.util.Utils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(Arquillian.class) +public class InputTransformerTest { + + @Deployment + public static Archive getDeployment() { + return ShrinkWrap.create(BeanArchive.class, Utils.getDeploymentNameAsHash(InputTransformerTest.class)) + .addPackage(InputTransformerTest.class.getPackage()) + .addAsServiceProvider(Extension.class, ObservingExtension.class); + } + + @Inject + ObservingExtension extension; + + @Inject + ActualBean bean; + + @Test + public void testArgTransformerAssignability() { + Beta result; + // test initial state without transformers + result = (Beta) extension.getNoTransformer().invoke(bean, new Object[] { 0 }); + Assert.assertEquals("0", result.ping()); + Assert.assertEquals(Integer.valueOf(0), result.getInteger()); + + // apply transformers, invoke method params are now String instead of original Number + result = (Beta) extension.getTransformArg1().invoke(bean, new Object[] { "42" }); + Assert.assertEquals("42", result.ping()); + Assert.assertEquals(Integer.valueOf(0), result.getInteger()); + result = (Beta) extension.getTransformArg2().invoke(bean, new Object[] { "42" }); + Assert.assertEquals("42", result.ping()); + Assert.assertEquals(Integer.valueOf(0), result.getInteger()); + } + + @Test + public void testInstanceTransformerAssignability() { + Beta result; + // test initial state without transformers + result = (Beta) extension.getNoTransformer().invoke(bean, new Object[] { 0 }); + Assert.assertEquals("0", result.ping()); + Assert.assertEquals(Integer.valueOf(0), result.getInteger()); + + // apply transformers, instance parameter is now null + result = (Beta) extension.getTransformInstance1().invoke(null, new Object[] { 42 }); + Assert.assertEquals("42", result.ping()); + Assert.assertEquals(Integer.valueOf(100), result.getInteger()); + result = (Beta) extension.getTransformInstance2().invoke(null, new Object[] { 42 }); + Assert.assertEquals("42", result.ping()); + Assert.assertEquals(Integer.valueOf(100), result.getInteger()); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/ObservingExtension.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/ObservingExtension.java new file mode 100644 index 0000000000..d0419c52e5 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/ObservingExtension.java @@ -0,0 +1,63 @@ +package org.jboss.weld.tests.invokable.transformers.input; + +import java.util.Collection; + +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.spi.AnnotatedMethod; +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.inject.spi.ProcessManagedBean; +import jakarta.enterprise.invoke.Invoker; + +import org.junit.Assert; + +public class ObservingExtension implements Extension { + + Invoker noTransformer; + Invoker transformInstance1; + Invoker transformInstance2; + Invoker transformArg1; + Invoker transformArg2; + + public Invoker getTransformInstance1() { + return transformInstance1; + } + + public Invoker getTransformInstance2() { + return transformInstance2; + } + + public Invoker getTransformArg1() { + return transformArg1; + } + + public Invoker getTransformArg2() { + return transformArg2; + } + + public Invoker getNoTransformer() { + return noTransformer; + } + + public void observe(@Observes ProcessManagedBean pmb) { + Collection> invokableMethods = pmb.getAnnotatedBeanClass().getMethods(); + Assert.assertEquals(2, invokableMethods.size()); + for (AnnotatedMethod invokableMethod : invokableMethods) { + if (invokableMethod.getJavaMember().getName().contains("ping")) { + noTransformer = pmb.createInvoker(invokableMethod).build(); + transformInstance1 = pmb.createInvoker(invokableMethod) + .setInstanceTransformer(Transformer.class, "transformInstance1") + .build(); + transformInstance2 = pmb.createInvoker(invokableMethod) + .setInstanceTransformer(Transformer.class, "transformInstance2") + .build(); + + transformArg1 = pmb.createInvoker(invokableMethod) + .setArgumentTransformer(0, Transformer.class, "transformArg1") + .build(); + transformArg2 = pmb.createInvoker(invokableMethod) + .setArgumentTransformer(0, Transformer.class, "transformArg2") + .build(); + } + } + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/Transformer.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/Transformer.java new file mode 100644 index 0000000000..9e94c5d028 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/input/Transformer.java @@ -0,0 +1,37 @@ +package org.jboss.weld.tests.invokable.transformers.input; + +import java.math.BigDecimal; + +/** + * Offers various transformation methods + */ +public class Transformer { + + // arg transformers + // different input, same output + public static Number transformArg1(String s) { + return Integer.valueOf(s); + } + + // different input, output is a subclass + public static BigDecimal transformArg2(String s) { + return BigDecimal.valueOf(Long.valueOf(s)); + } + + // instance transformers + // different input, same output + public static ActualBean transformInstance1(Integer i) { + if (i != null) { + throw new IllegalArgumentException("Should never happen as nothing but null passes for an Integer"); + } + return new ActualBean(100); + } + + // different input, output is a subclass + public static AugmentedBean transformInstance2(Integer i) { + if (i != null) { + throw new IllegalArgumentException("Should never happen as nothing but null passes for an Integer"); + } + return new AugmentedBean(100); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/ActualBean.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/ActualBean.java new file mode 100644 index 0000000000..c1ec7b78df --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/ActualBean.java @@ -0,0 +1,11 @@ +package org.jboss.weld.tests.invokable.transformers.output; + +import jakarta.enterprise.context.Dependent; + +@Dependent +public class ActualBean { + + public Beta ping(Number n) { + return new Gamma(n, 0); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/Alpha.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/Alpha.java new file mode 100644 index 0000000000..6e913034ef --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/Alpha.java @@ -0,0 +1,14 @@ +package org.jboss.weld.tests.invokable.transformers.output; + +public class Alpha { + + protected Number n; + + public Alpha() { + this.n = 0; + } + + public String ping() { + return n.toString(); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/Beta.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/Beta.java new file mode 100644 index 0000000000..458f0664b7 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/Beta.java @@ -0,0 +1,20 @@ +package org.jboss.weld.tests.invokable.transformers.output; + +public class Beta extends Alpha { + + protected Integer i; + + public Beta() { + super(); + this.i = 0; + } + + public Beta(Number n, Integer i) { + this.n = n; + this.i = i; + } + + public Integer getInteger() { + return i; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/ExceptionalBean.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/ExceptionalBean.java new file mode 100644 index 0000000000..1ece114dcd --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/ExceptionalBean.java @@ -0,0 +1,11 @@ +package org.jboss.weld.tests.invokable.transformers.output; + +import jakarta.enterprise.context.ApplicationScoped; + +@ApplicationScoped +public class ExceptionalBean { + + public Beta ping(Integer i) { + throw new IllegalArgumentException("expected"); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/Gamma.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/Gamma.java new file mode 100644 index 0000000000..174bb77852 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/Gamma.java @@ -0,0 +1,11 @@ +package org.jboss.weld.tests.invokable.transformers.output; + +public class Gamma extends Beta { + public Gamma(Number n, Integer i) { + super(n, i); + } + + public Gamma() { + super(); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/ObservingExtension.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/ObservingExtension.java new file mode 100644 index 0000000000..d05eb4d1ef --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/ObservingExtension.java @@ -0,0 +1,74 @@ +package org.jboss.weld.tests.invokable.transformers.output; + +import java.util.Collection; + +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.spi.AnnotatedMethod; +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.inject.spi.ProcessManagedBean; +import jakarta.enterprise.invoke.Invoker; + +import org.junit.Assert; + +public class ObservingExtension implements Extension { + + Invoker noTransformer; + Invoker transformReturnType1; + Invoker transformReturnType2; + Invoker transformException1; + Invoker transformException2; + Invoker transformException3; + + public Invoker getTransformReturnType1() { + return transformReturnType1; + } + + public Invoker getTransformReturnType2() { + return transformReturnType2; + } + + public Invoker getTransformException1() { + return transformException1; + } + + public Invoker getTransformException2() { + return transformException2; + } + + public Invoker getTransformException3() { + return transformException3; + } + + public Invoker getNoTransformer() { + return noTransformer; + } + + public void observe(@Observes ProcessManagedBean pmb) { + Collection> invokableMethods = pmb.getAnnotatedBeanClass().getMethods(); + Assert.assertEquals(1, invokableMethods.size()); + AnnotatedMethod invokableMethod = invokableMethods.iterator().next(); + noTransformer = pmb.createInvoker(invokableMethod).build(); + transformReturnType1 = pmb.createInvoker(invokableMethod) + .setReturnValueTransformer(Transformer.class, "transformReturn1") + .build(); + transformReturnType2 = pmb.createInvoker(invokableMethod) + .setReturnValueTransformer(Transformer.class, "transformReturn2") + .build(); + } + + public void observeExceptionally(@Observes ProcessManagedBean pmb) { + Collection> invokableMethods = pmb.getAnnotatedBeanClass().getMethods(); + Assert.assertEquals(1, invokableMethods.size()); + AnnotatedMethod invokableMethod = invokableMethods.iterator().next(); + + transformException1 = pmb.createInvoker(invokableMethod) + .setExceptionTransformer(Transformer.class, "transformException1") + .build(); + transformException2 = pmb.createInvoker(invokableMethod) + .setExceptionTransformer(Transformer.class, "transformException2") + .build(); + transformException3 = pmb.createInvoker(invokableMethod) + .setExceptionTransformer(Transformer.class, "transformException3") + .build(); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/OutputTransformerTest.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/OutputTransformerTest.java new file mode 100644 index 0000000000..7eded40861 --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/OutputTransformerTest.java @@ -0,0 +1,72 @@ +package org.jboss.weld.tests.invokable.transformers.output; + +import jakarta.enterprise.inject.spi.Extension; +import jakarta.inject.Inject; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.junit.Arquillian; +import org.jboss.shrinkwrap.api.Archive; +import org.jboss.shrinkwrap.api.BeanArchive; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.weld.test.util.Utils; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(Arquillian.class) +public class OutputTransformerTest { + + @Deployment + public static Archive getDeployment() { + return ShrinkWrap.create(BeanArchive.class, Utils.getDeploymentNameAsHash(OutputTransformerTest.class)) + .addPackage(OutputTransformerTest.class.getPackage()) + .addAsServiceProvider(Extension.class, ObservingExtension.class); + } + + @Inject + ObservingExtension extension; + + @Inject + ActualBean bean; + + @Inject + ExceptionalBean exceptionalBean; + + @Test + public void testReturnTypeTransformerAssignability() { + Beta betaResult; + // test initial state without transformers + betaResult = (Beta) extension.getNoTransformer().invoke(bean, new Object[] { 0 }); + Assert.assertEquals("0", betaResult.ping()); + Assert.assertEquals(Integer.valueOf(0), betaResult.getInteger()); + + // apply transformers, first one returns Beta, the other just String + Object result; + result = extension.getTransformReturnType1().invoke(bean, new Object[] { 10 }); + Assert.assertTrue(result instanceof Beta); + Assert.assertEquals("42", ((Beta) result).ping()); + Assert.assertEquals(Integer.valueOf(42), ((Beta) result).getInteger()); + result = extension.getTransformReturnType2().invoke(bean, new Object[] { 23 }); + Assert.assertTrue(result instanceof String); + Assert.assertEquals("230", result.toString()); + } + + @Test + public void testExceptionTransformerAssignability() { + // apply transformers, first one swallows exception and returns Beta + Object result; + result = extension.getTransformException1().invoke(exceptionalBean, new Object[] { 10 }); + Assert.assertTrue(result instanceof Beta); + Assert.assertEquals("42", ((Beta) result).ping()); + Assert.assertEquals(Integer.valueOf(42), ((Beta) result).getInteger()); + // second transformer returns a subclas + result = extension.getTransformException2().invoke(exceptionalBean, new Object[] { 23 }); + Assert.assertTrue(result instanceof Gamma); + Assert.assertEquals("42", ((Gamma) result).ping()); + Assert.assertEquals(Integer.valueOf(42), ((Gamma) result).getInteger()); + // third transformer returns a completely different type + result = extension.getTransformException3().invoke(exceptionalBean, new Object[] { 23 }); + Assert.assertTrue(result instanceof String); + Assert.assertEquals("foobar", result); + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/Transformer.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/Transformer.java new file mode 100644 index 0000000000..1a6547e9ac --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/transformers/output/Transformer.java @@ -0,0 +1,35 @@ +package org.jboss.weld.tests.invokable.transformers.output; + +/** + * Offers various transformation methods + */ +public class Transformer { + + // return type transformers + // same return value, input value is superclass + public static Beta transformReturn1(Alpha alpha) { + return new Beta(42, 42); + } + + // return value is completely different, input value is identical + public static String transformReturn2(Beta beta) { + return beta.ping() + beta.getInteger(); + } + + // exception transformers + // same return type + public static Beta transformException1(Throwable t) { + return new Beta(42, 42); + } + + // return type is a subclass + // note that exception transformers (via method handles) cannot have completely different ret. types + public static Gamma transformException2(Throwable t) { + return new Gamma(42, 42); + } + + // return type is completely different + public static String transformException3(Throwable t) { + return "foobar"; + } +} diff --git a/weld-lite-extension-translator/src/main/java/org/jboss/weld/lite/extension/translator/BeanInfoImpl.java b/weld-lite-extension-translator/src/main/java/org/jboss/weld/lite/extension/translator/BeanInfoImpl.java index b2627cef3e..722cf811d8 100644 --- a/weld-lite-extension-translator/src/main/java/org/jboss/weld/lite/extension/translator/BeanInfoImpl.java +++ b/weld-lite-extension-translator/src/main/java/org/jboss/weld/lite/extension/translator/BeanInfoImpl.java @@ -7,16 +7,20 @@ import jakarta.enterprise.inject.build.compatible.spi.BeanInfo; import jakarta.enterprise.inject.build.compatible.spi.DisposerInfo; import jakarta.enterprise.inject.build.compatible.spi.InjectionPointInfo; +import jakarta.enterprise.inject.build.compatible.spi.InvokerInfo; import jakarta.enterprise.inject.build.compatible.spi.ScopeInfo; import jakarta.enterprise.inject.build.compatible.spi.StereotypeInfo; +import jakarta.enterprise.inject.spi.AnnotatedMethod; import jakarta.enterprise.inject.spi.BeanManager; import jakarta.enterprise.inject.spi.InjectionPoint; +import jakarta.enterprise.invoke.InvokerBuilder; import jakarta.enterprise.lang.model.AnnotationInfo; import jakarta.enterprise.lang.model.declarations.ClassInfo; import jakarta.enterprise.lang.model.declarations.FieldInfo; import jakarta.enterprise.lang.model.declarations.MethodInfo; import jakarta.enterprise.lang.model.types.Type; +import org.jboss.weld.invokable.InvokerInfoBuilder; import org.jboss.weld.lite.extension.translator.util.reflection.AnnotatedTypes; class BeanInfoImpl implements BeanInfo { @@ -145,6 +149,23 @@ public Collection injectionPoints() { .collect(Collectors.toList()); } + @Override + public InvokerBuilder createInvoker(MethodInfo methodInfo) { + if (methodInfo.isConstructor()) { + // TODO better exception + throw new IllegalArgumentException( + "Constructor methods are not valid candidates for Invokers. MethodInfo: " + methodInfo); + } + if (methodInfo instanceof MethodInfoImpl) { + // at this point we can be sure it is a Method, not a Constructor, so we cast it + AnnotatedMethod cdiMethod = (AnnotatedMethod) ((MethodInfoImpl) methodInfo).cdiDeclaration; + return new InvokerInfoBuilder<>(cdiBean.getBeanClass(), cdiMethod.getJavaMember(), bm); + } else { + // TODO better exception + throw new IllegalArgumentException("Custom implementations of MethodInfo are not supported!"); + } + } + @Override public String toString() { return "@" + cdiBean.getScope().getSimpleName() + " bean [types=" + cdiBean.getTypes() diff --git a/weld-lite-extension-translator/src/main/java/org/jboss/weld/lite/extension/translator/SyntheticComponentBuilderBase.java b/weld-lite-extension-translator/src/main/java/org/jboss/weld/lite/extension/translator/SyntheticComponentBuilderBase.java index e745ba3b99..7c599183bd 100644 --- a/weld-lite-extension-translator/src/main/java/org/jboss/weld/lite/extension/translator/SyntheticComponentBuilderBase.java +++ b/weld-lite-extension-translator/src/main/java/org/jboss/weld/lite/extension/translator/SyntheticComponentBuilderBase.java @@ -4,10 +4,11 @@ import java.util.HashMap; import java.util.Map; +import jakarta.enterprise.inject.build.compatible.spi.InvokerInfo; import jakarta.enterprise.lang.model.AnnotationInfo; import jakarta.enterprise.lang.model.declarations.ClassInfo; -class SyntheticComponentBuilderBase> { +public class SyntheticComponentBuilderBase> { final Map params = new HashMap<>(); @SuppressWarnings("unchecked") @@ -122,4 +123,14 @@ public THIS withParam(String key, Annotation[] value) { this.params.put(key, value); return self(); } + + public THIS withParam(String key, InvokerInfo value) { + this.params.put(key, value); + return self(); + } + + public THIS withParam(String key, InvokerInfo[] value) { + this.params.put(key, value); + return self(); + } }