diff --git a/impl/src/main/java/org/jboss/weld/invokable/AbstractInvokerBuilder.java b/impl/src/main/java/org/jboss/weld/invokable/AbstractInvokerBuilder.java index 9cae53cc633..ed7fc7b139d 100644 --- a/impl/src/main/java/org/jboss/weld/invokable/AbstractInvokerBuilder.java +++ b/impl/src/main/java/org/jboss/weld/invokable/AbstractInvokerBuilder.java @@ -136,6 +136,19 @@ private boolean requiresCleanup() { // single, array-typed parameter at the end for variable arity methods mh = mh.asFixedArity(); + // Check instance is not null + if (!instanceLookup && !isStaticMethod) { + Class instanceType = mh.type().parameterType(0); + Class returnType = mh.type().returnType(); + MethodHandle checkInstanceNotNull = MethodHandles.insertArguments(MethodHandleUtils.CHECK_INSTANCE_NOT_NULL, 0, + reflectionMethod); + checkInstanceNotNull = checkInstanceNotNull + .asType(checkInstanceNotNull.type().changeParameterType(0, instanceType)); + MethodHandle npeCatch = MethodHandles.throwException(returnType, NullPointerException.class); + npeCatch = MethodHandles.collectArguments(npeCatch, 1, checkInstanceNotNull); + mh = MethodHandles.catchException(mh, NullPointerException.class, npeCatch); + } + // instance transformer if (instanceTransformer != null && !isStaticMethod) { MethodHandle instanceTransformerMethod = MethodHandleUtils.createMethodHandleFromTransformer(reflectionMethod, @@ -155,8 +168,10 @@ private boolean requiresCleanup() { // argument transformers // backwards iteration for correct construction of the resulting parameter list + Class[] transformerArgTypes = new Class[argTransformers.length]; for (int i = argTransformers.length - 1; i >= 0; i--) { if (argTransformers[i] == null) { + transformerArgTypes[i] = reflectionMethod.getParameterTypes()[i]; continue; } int position = instanceArguments + i; @@ -172,6 +187,7 @@ private boolean requiresCleanup() { // internal error, this should not pass validation throw InvokerLogger.LOG.invalidTransformerMethod("argument", argTransformers[i]); } + transformerArgTypes[i] = argTransformerMethod.type().parameterType(0); } // return type transformer @@ -323,6 +339,61 @@ private boolean requiresCleanup() { mh = MethodHandles.foldArguments(mh, MethodHandleUtils.CLEANUP_ACTIONS_CTOR); } + Class[] expectedTypes = new Class[transformerArgTypes.length]; + for (int i = 0; i < transformerArgTypes.length; i++) { + if (argLookup[i]) { + expectedTypes[i] = null; + } else { + expectedTypes[i] = transformerArgTypes[i]; + } + } + + if (reflectionMethod.getParameterCount() > 0) { + // Catch NullPointerException and check whether it's caused by any arguments being null: + Class instanceType = mh.type().parameterType(0); + MethodHandle checkArgumentsNotNull = MethodHandles.insertArguments(MethodHandleUtils.CHECK_ARGUMENTS_NOT_NULL, 0, + reflectionMethod, expectedTypes); + checkArgumentsNotNull = MethodHandles.dropArguments(checkArgumentsNotNull, 0, instanceType); + MethodHandle npeCatch = MethodHandles.throwException(mh.type().returnType(), NullPointerException.class); + npeCatch = MethodHandles.collectArguments(npeCatch, 1, checkArgumentsNotNull); + mh = MethodHandles.catchException(mh, NullPointerException.class, npeCatch); + + // Catch IllegalArgumentException and check whether it's caused by the args array being too short + MethodHandle checkArgCountAtLeast = MethodHandles.insertArguments(MethodHandleUtils.CHECK_ARG_COUNT_AT_LEAST, 0, + reflectionMethod, reflectionMethod.getParameterCount()); + checkArgCountAtLeast = MethodHandles.dropArguments(checkArgCountAtLeast, 0, instanceType); + MethodHandle iaeCatch = MethodHandles.throwException(mh.type().returnType(), IllegalArgumentException.class); + iaeCatch = MethodHandles.collectArguments(iaeCatch, 1, checkArgCountAtLeast); + mh = MethodHandles.catchException(mh, IllegalArgumentException.class, iaeCatch); + } + + if ((!isStaticMethod && !instanceLookup) || reflectionMethod.getParameterCount() > 0) { + // Catch ClassCastException and check whether it's caused by either the instance or the arguments being the wrong type + Class instanceType = mh.type().parameterType(0); + MethodHandle checkTypes = null; + if (reflectionMethod.getParameterCount() > 0) { + checkTypes = MethodHandles.insertArguments(MethodHandleUtils.CHECK_ARGUMENTS_HAVE_CORRECT_TYPE, 0, + reflectionMethod, expectedTypes); + checkTypes = MethodHandles.dropArguments(checkTypes, 0, Object.class); + } + + if (!isStaticMethod && !instanceLookup) { + MethodHandle checkInstanceType = MethodHandles.insertArguments(MethodHandleUtils.CHECK_INSTANCE_HAS_TYPE, 0, + reflectionMethod, instanceType); + if (checkTypes == null) { + checkTypes = checkInstanceType; + } else { + checkTypes = MethodHandles.foldArguments(checkTypes, checkInstanceType); + } + } + + MethodHandle cceCatch = MethodHandles.throwException(mh.type().returnType(), ClassCastException.class); + cceCatch = MethodHandles.collectArguments(cceCatch, 1, checkTypes); + + mh = mh.asType(mh.type().changeParameterType(0, Object.class)); // Defer the casting the instance to its expected type until we're inside the ClassCastException try block + mh = MethodHandles.catchException(mh, ClassCastException.class, cceCatch); + } + // create an inner invoker and pass it to wrapper if (invocationWrapper != null) { InvokerImpl invoker = new InvokerImpl<>(mh); diff --git a/impl/src/main/java/org/jboss/weld/invokable/InvokerValidationUtils.java b/impl/src/main/java/org/jboss/weld/invokable/InvokerValidationUtils.java new file mode 100644 index 00000000000..2742ea17700 --- /dev/null +++ b/impl/src/main/java/org/jboss/weld/invokable/InvokerValidationUtils.java @@ -0,0 +1,182 @@ +package org.jboss.weld.invokable; + +import java.lang.reflect.Method; + +import jakarta.enterprise.invoke.Invoker; + +import org.jboss.weld.exceptions.IllegalArgumentException; +import org.jboss.weld.logging.InvokerLogger; + +/** + * Utility methods for checking the arguments and instances being used by an {@link Invoker}. + *

+ * Handles to these methods are obtained via {@link MethodHandleUtils}. + */ +public class InvokerValidationUtils { + + private InvokerValidationUtils() { + } + + /** + * Validate that an instance being called by an {@link Invoker} has the expected type or is {@code null}. + * + * @param invokerMethod the method being invoked + * @param type the expected type of the instance + * @param instance the instance the method is being invoked on + * @throws ClassCastException if {@code instance} is not {@code null} and not of the expected type + */ + static void instanceHasType(Method invokerMethod, Class type, Object instance) { + if (instance != null && !type.isInstance(instance)) { + throw InvokerLogger.LOG.wrongInstanceType(invokerMethod, instance.getClass(), type); + } + } + + /** + * Validate that an instance being called by an {@link Invoker} is not {@code null}. + * + * @param invokerMethod the method being invoked + * @param instance the instance to check + * @throws NullPointerException if {@code instance} is {@code null} + */ + static void instanceNotNull(Method invokerMethod, Object instance) { + if (instance == null) { + throw InvokerLogger.LOG.nullInstance(invokerMethod); + } + } + + /** + * Validate that if arguments are required then the arguments array is not {@code null} and any primitive arguments are not + * {@code null}. + * + * @param invokerMethod the method being invoked + * @param expectedTypes the expected type of each argument, a {@code null} entry indicates that the type of that parameter + * should not be checked + * @param args the array of arguments + * @throws NullPointerException if arguments are required and {@code args} is {@code null} or a primitive argument is + * {@code null} + */ + static void argumentsNotNull(Method invokerMethod, Class[] expectedTypes, Object[] args) { + if (invokerMethod.getParameterCount() == 0) { + return; // If there are no arguments expected then there's nothing to check + } + if (args == null) { + throw InvokerLogger.LOG.nullArgumentArray(invokerMethod); + } + for (int i = 0; i < expectedTypes.length; i++) { + Class expectedType = expectedTypes[i]; + Object arg = args[i]; + if (expectedType != null && arg == null && expectedType.isPrimitive()) { + throw InvokerLogger.LOG.nullPrimitiveArgument(invokerMethod, i + 1); + } + } + } + + /** + * Validate that an array of arguments for an {@link Invoker} has at least an expected number of elements + * + * @param invokerMethod the method being invoked + * @param requiredArgs the expected number of arguments + * @param args the array of arguments + * @return {@code args} + * @throws IllegalArgumentException if the length of {@code args} is less than {@code requiredArgs} + */ + static void argCountAtLeast(Method invokerMethod, int requiredArgs, Object[] args) { + int actualArgs = args == null ? 0 : args.length; + if (actualArgs < requiredArgs) { + throw InvokerLogger.LOG.notEnoughArguments(invokerMethod, requiredArgs, actualArgs); + } + } + + /** + * Validate that each of the arguments being passed passed to a method by an {@link Invoker} has the correct type. + *

+ * For each pair if type and argument from {@code expectedTypes} and {@code args}: + *

+ * + * @param invokerMethod the method being invoked + * @param expectedTypes an array of the expected type of each argument. May contain {@code null} to indicate that that + * argument should not be validated. + * @param args the array of values being passed as arguments + * @throws ClassCastException if any of the arguments are not valid for their expected type + * @throws NullPointerException if an argument for a primitive-typed parameter is not null + */ + static void argumentsHaveCorrectType(Method invokerMethod, Class[] expectedTypes, Object[] args) { + if (args == null) { + // Not expected since we're in the catch block of ClassCastException, but guarantees we can safely access args + throw InvokerLogger.LOG.nullArgumentArray(invokerMethod); + } + for (int i = 0; i < expectedTypes.length; i++) { + Class expectedType = expectedTypes[i]; + Object arg = args[i]; + if (expectedType != null) { + int pos = i + 1; // 1-indexed argument position + if (expectedType.isPrimitive()) { + if (arg == null) { + // Not expected since we're in the catch block of ClassCastException, but guarantees we can safely call methods on arg + throw InvokerLogger.LOG.nullPrimitiveArgument(invokerMethod, pos); + } + if (!primitiveConversionPermitted(expectedType, arg.getClass())) { + throw InvokerLogger.LOG.wrongArgumentType(invokerMethod, pos, arg.getClass(), expectedType); + } + } else { + if (arg != null && !expectedType.isInstance(arg)) { + throw InvokerLogger.LOG.wrongArgumentType(invokerMethod, pos, arg.getClass(), expectedType); + } + } + } + } + } + + /** + * Validate whether a reference type can be converted to a primitive type via an unboxing and primitive widening conversion. + * + * @param primitive the target primitive type + * @param actual the reference type to test + * @return {@code true} if {@code actual} can be converted to {@code primitive} via an unboxing and primitive widening + * conversion, otherwise {@code false} + */ + private static boolean primitiveConversionPermitted(Class primitive, Class actual) { + if (primitive == Integer.TYPE) { + return actual == Integer.class + || actual == Character.class + || actual == Short.class + || actual == Byte.class; + } else if (primitive == Long.TYPE) { + return actual == Long.class + || actual == Integer.class + || actual == Character.class + || actual == Short.class + || actual == Byte.class; + } else if (primitive == Boolean.TYPE) { + return actual == Boolean.class; + } else if (primitive == Double.TYPE) { + return actual == Double.class + || actual == Float.class + || actual == Long.class + || actual == Integer.class + || actual == Character.class + || actual == Short.class + || actual == Byte.class; + } else if (primitive == Float.TYPE) { + return actual == Float.class + || actual == Long.class + || actual == Integer.class + || actual == Character.class + || actual == Short.class + || actual == Byte.class; + } else if (primitive == Short.TYPE) { + return actual == Short.class + || actual == Byte.class; + } else if (primitive == Character.TYPE) { + return actual == Character.class; + } else if (primitive == Byte.TYPE) { + return actual == Byte.class; + } + throw new RuntimeException("Unhandled primitive type: " + primitive); + } +} diff --git a/impl/src/main/java/org/jboss/weld/invokable/MethodHandleUtils.java b/impl/src/main/java/org/jboss/weld/invokable/MethodHandleUtils.java index d49f482611f..658cfcf1ec4 100644 --- a/impl/src/main/java/org/jboss/weld/invokable/MethodHandleUtils.java +++ b/impl/src/main/java/org/jboss/weld/invokable/MethodHandleUtils.java @@ -28,6 +28,11 @@ private MethodHandleUtils() { static final MethodHandle REPLACE_PRIMITIVE_LOOKUP_NULLS; static final MethodHandle THROW_VALUE_CARRYING_EXCEPTION; static final MethodHandle TRIM_ARRAY_TO_SIZE; + static final MethodHandle CHECK_INSTANCE_HAS_TYPE; + static final MethodHandle CHECK_INSTANCE_NOT_NULL; + static final MethodHandle CHECK_ARG_COUNT_AT_LEAST; + static final MethodHandle CHECK_ARGUMENTS_HAVE_CORRECT_TYPE; + static final MethodHandle CHECK_ARGUMENTS_NOT_NULL; static { try { @@ -45,6 +50,16 @@ private MethodHandleUtils() { "throwReturnValue", Object.class)); TRIM_ARRAY_TO_SIZE = createMethodHandle(ArrayUtils.class.getDeclaredMethod( "trimArrayToSize", Object[].class, int.class)); + CHECK_INSTANCE_HAS_TYPE = createMethodHandle( + InvokerValidationUtils.class.getDeclaredMethod("instanceHasType", Method.class, Class.class, Object.class)); + CHECK_INSTANCE_NOT_NULL = createMethodHandle( + InvokerValidationUtils.class.getDeclaredMethod("instanceNotNull", Method.class, Object.class)); + CHECK_ARG_COUNT_AT_LEAST = createMethodHandle( + InvokerValidationUtils.class.getDeclaredMethod("argCountAtLeast", Method.class, int.class, Object[].class)); + CHECK_ARGUMENTS_HAVE_CORRECT_TYPE = createMethodHandle(InvokerValidationUtils.class + .getDeclaredMethod("argumentsHaveCorrectType", Method.class, Class[].class, Object[].class)); + CHECK_ARGUMENTS_NOT_NULL = createMethodHandle(InvokerValidationUtils.class.getDeclaredMethod("argumentsNotNull", + Method.class, Class[].class, Object[].class)); } catch (NoSuchMethodException e) { // should never happen throw new IllegalStateException("Unable to locate Weld internal helper method", e); diff --git a/impl/src/main/java/org/jboss/weld/logging/InvokerLogger.java b/impl/src/main/java/org/jboss/weld/logging/InvokerLogger.java index eef7515691e..32182a620eb 100644 --- a/impl/src/main/java/org/jboss/weld/logging/InvokerLogger.java +++ b/impl/src/main/java/org/jboss/weld/logging/InvokerLogger.java @@ -5,6 +5,7 @@ import org.jboss.logging.Logger; import org.jboss.logging.annotations.Cause; import org.jboss.logging.annotations.Message; +import org.jboss.logging.annotations.Message.Format; import org.jboss.logging.annotations.MessageLogger; import org.jboss.weld.exceptions.DeploymentException; import org.jboss.weld.exceptions.IllegalArgumentException; @@ -65,4 +66,22 @@ public interface InvokerLogger extends WeldLogger { @Message(id = 2014, value = "Invocation wrapper has unexpected parameters: {0} \nExpected param types are: {1}, Object[], Invoker.class", format = Message.Format.MESSAGE_FORMAT) DeploymentException wrapperUnexpectedParams(Object transformerMetadata, Object clazz); + + @Message(id = 2015, value = "Cannot invoke {0} because the instance passed to the Invoker was null", format = Format.MESSAGE_FORMAT) + NullPointerException nullInstance(Object method); + + @Message(id = 2016, value = "Cannot invoke {0} because the instance passed to the Invoker has type {1} which cannot be cast to {2}", format = Format.MESSAGE_FORMAT) + ClassCastException wrongInstanceType(Object method, Class actualType, Class expectedType); + + @Message(id = 2017, value = "Cannot invoke {0} because {1} arguments were expected but only {2} were provided", format = Format.MESSAGE_FORMAT) + IllegalArgumentException notEnoughArguments(Object method, int expectedCount, int actualCount); + + @Message(id = 2018, value = "Cannot invoke {0} because argument {1} has type {2} which cannot be cast to {3}", format = Format.MESSAGE_FORMAT) + ClassCastException wrongArgumentType(Object method, int pos, Class actualType, Class expectedType); + + @Message(id = 2019, value = "Cannot invoke {0} because parameter {1} is a primitive type but the argument is null", format = Format.MESSAGE_FORMAT) + NullPointerException nullPrimitiveArgument(Object method, int pos); + + @Message(id = 2020, value = "Cannot invoke {0} because the args parameter is null and arguments are required", format = Format.MESSAGE_FORMAT) + NullPointerException nullArgumentArray(Object method); } diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/exceptions/ExceptionTestBean.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/exceptions/ExceptionTestBean.java new file mode 100644 index 00000000000..f079748c76b --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/exceptions/ExceptionTestBean.java @@ -0,0 +1,23 @@ +package org.jboss.weld.tests.invokable.exceptions; + +import jakarta.enterprise.context.Dependent; + +@Dependent +public class ExceptionTestBean { + + public String ping(String s, int i) { + return s + i; + } + + public static String staticPing(String s, int i) { + return s + i; + } + + public void voidPing(String s, int i) { + String result = s + i; + } + + public String noargPing() { + return "42"; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/exceptions/InvokableExceptionsTest.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/exceptions/InvokableExceptionsTest.java new file mode 100644 index 00000000000..df50c8632ea --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/exceptions/InvokableExceptionsTest.java @@ -0,0 +1,200 @@ +package org.jboss.weld.tests.invokable.exceptions; + +import jakarta.enterprise.inject.spi.Extension; +import jakarta.enterprise.invoke.Invoker; +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 InvokableExceptionsTest { + + @Deployment + public static Archive getDeployment() { + return ShrinkWrap.create(BeanArchive.class, Utils.getDeploymentNameAsHash(InvokableExceptionsTest.class)) + .addPackage(InvokableExceptionsTest.class.getPackage()) + .addAsServiceProvider(Extension.class, ObservingExtension.class); + } + + @Inject + ObservingExtension extension; + + @Inject + ExceptionTestBean bean; + + @Test + public void testNullPrimitiveArgument() { + assertException(() -> extension.getPingInvoker().invoke(bean, new Object[] { "foo", null }), + NullPointerException.class, + "WELD-002019"); + + assertException(() -> extension.getStaticPingInvoker().invoke(null, new Object[] { "foo", null }), + NullPointerException.class, + "WELD-002019"); + + assertException(() -> extension.getVoidPingInvoker().invoke(bean, new Object[] { "foo", null }), + NullPointerException.class, + "WELD-002019"); + } + + @Test + public void testWrongReferenceArgumentType() { + assertException(() -> extension.getPingInvoker().invoke(bean, new Object[] { new Object(), 2 }), + ClassCastException.class, + "WELD-002018"); + + assertException(() -> extension.getStaticPingInvoker().invoke(bean, new Object[] { new Object(), 2 }), + ClassCastException.class, + "WELD-002018"); + + assertException(() -> extension.getVoidPingInvoker().invoke(bean, new Object[] { new Object(), 2 }), + ClassCastException.class, + "WELD-002018"); + } + + @Test + public void testWrongPrimitiveArgumentType() throws Exception { + assertException(() -> extension.getPingInvoker().invoke(bean, new Object[] { "foo", new Object() }), + ClassCastException.class, + "WELD-002018"); + + assertException(() -> extension.getStaticPingInvoker().invoke(bean, new Object[] { "foo", new Object() }), + ClassCastException.class, + "WELD-002018"); + + assertException(() -> extension.getVoidPingInvoker().invoke(bean, new Object[] { "foo", new Object() }), + ClassCastException.class, + "WELD-002018"); + + // Narrowing conversion results in ClassCastException + assertException(() -> extension.getPingInvoker().invoke(bean, new Object[] { "foo", 4L }), + ClassCastException.class, + "WELD-002018"); + + // Widening version is permitted + extension.getPingInvoker().invoke(bean, new Object[] { "foo", (short) 4 }); + } + + @Test + public void testNullArgumentsArray() throws Exception { + assertException(() -> extension.getPingInvoker().invoke(bean, null), + NullPointerException.class, + "WELD-002020"); + + assertException(() -> extension.getStaticPingInvoker().invoke(bean, null), + NullPointerException.class, + "WELD-002020"); + + assertException(() -> extension.getVoidPingInvoker().invoke(bean, null), + NullPointerException.class, + "WELD-002020"); + + // Args array can be null if the method has no parameters + extension.getNoargPingInvoker().invoke(bean, null); + } + + @Test + public void testNullInstance() { + assertException(() -> extension.getPingInvoker().invoke(null, new Object[] { "foo", 2 }), + NullPointerException.class, + "WELD-002015"); + + assertException(() -> extension.getVoidPingInvoker().invoke(null, new Object[] { "foo", 2 }), + NullPointerException.class, + "WELD-002015"); + + assertException(() -> extension.getNoargPingInvoker().invoke(null, new Object[] {}), + NullPointerException.class, + "WELD-002015"); + } + + @Test + public void testMissingArguments() { + assertException(() -> extension.getPingInvoker().invoke(bean, new Object[] { "foo" }), + IllegalArgumentException.class, + "WELD-002017"); + assertException(() -> extension.getStaticPingInvoker().invoke(bean, new Object[] { "foo" }), + IllegalArgumentException.class, + "WELD-002017"); + assertException(() -> extension.getVoidPingInvoker().invoke(bean, new Object[] { "foo" }), + IllegalArgumentException.class, + "WELD-002017"); + } + + @Test + public void testWrongInstanceType() throws Exception { + Invoker pingInvoker = (Invoker) (Invoker) extension.getPingInvoker(); + assertException(() -> pingInvoker.invoke(new Object(), new Object[] { "foo", 2 }), + ClassCastException.class, + "WELD-002016"); + Invoker voidPingInvoker = (Invoker) (Invoker) extension.getVoidPingInvoker(); + assertException(() -> voidPingInvoker.invoke(new Object(), new Object[] { "foo", 2 }), + ClassCastException.class, + "WELD-002016"); + Invoker noargPingInvoker = (Invoker) (Invoker) extension.getVoidPingInvoker(); + assertException(() -> noargPingInvoker.invoke(new Object(), new Object[] {}), + ClassCastException.class, + "WELD-002016"); + + // Anything can be passed as the instance to a static method + Invoker staticPingInvoker = (Invoker) (Invoker) extension.getStaticPingInvoker(); + staticPingInvoker.invoke(new Object(), new Object[] { "foo", 2 }); + } + + @Test + public void testCustomNumberArgument() { + class MyCustomThree extends Number { + + private static final long serialVersionUID = 3L; + + @Override + public double doubleValue() { + return 3d; + } + + @Override + public float floatValue() { + return 3f; + } + + @Override + public int intValue() { + return 3; + } + + @Override + public long longValue() { + return 3L; + } + } + + // Check that a custom Number implementation cannot be unboxed and so results in a ClassCastException + assertException(() -> extension.getPingInvoker().invoke(bean, new Object[] { "foo", new MyCustomThree() }), + ClassCastException.class, + "WELD-002018"); + } + + private void assertException(ThrowingRunnable runnable, Class exceptionType, String messageContains) { + try { + runnable.run(); + Assert.fail("No exception thrown"); + } catch (Exception e) { + if (!exceptionType.isInstance(e)) { + throw new AssertionError("Expected " + exceptionType + " but got " + e, e); + } + Assert.assertTrue("Exception message did not contain " + messageContains, e.getMessage().contains(messageContains)); + } + } + + private interface ThrowingRunnable { + void run() throws Exception; + } +} diff --git a/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/exceptions/ObservingExtension.java b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/exceptions/ObservingExtension.java new file mode 100644 index 00000000000..5339361b9df --- /dev/null +++ b/tests-arquillian/src/test/java/org/jboss/weld/tests/invokable/exceptions/ObservingExtension.java @@ -0,0 +1,50 @@ +package org.jboss.weld.tests.invokable.exceptions; + +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; + +public class ObservingExtension implements Extension { + + private Invoker pingInvoker; + private Invoker staticPingInvoker; + private Invoker voidPingInvoker; + private Invoker noargPingInvoker; + + public Invoker getPingInvoker() { + return pingInvoker; + } + + public Invoker getStaticPingInvoker() { + return staticPingInvoker; + } + + public Invoker getVoidPingInvoker() { + return voidPingInvoker; + } + + public Invoker getNoargPingInvoker() { + return noargPingInvoker; + } + + public void createInvoker(@Observes ProcessManagedBean pmb) { + AnnotatedMethod pingMethod = getMethod(pmb, "ping"); + pingInvoker = pmb.createInvoker(pingMethod).build(); + + AnnotatedMethod staticPingMethod = getMethod(pmb, "staticPing"); + staticPingInvoker = pmb.createInvoker(staticPingMethod).build(); + + AnnotatedMethod voidPingMethod = getMethod(pmb, "voidPing"); + voidPingInvoker = pmb.createInvoker(voidPingMethod).build(); + + AnnotatedMethod noargPingMethod = getMethod(pmb, "noargPing"); + noargPingInvoker = pmb.createInvoker(noargPingMethod).build(); + } + + private AnnotatedMethod getMethod(ProcessManagedBean event, String methodName) { + return event.getAnnotatedBeanClass().getMethods().stream() + .filter(m -> m.getJavaMember().getName().equals(methodName)).findFirst().get(); + } +}