Skip to content

Commit

Permalink
Add workaround enabling exception transformers to return arbitrary re…
Browse files Browse the repository at this point in the history
…turn value
  • Loading branch information
manovotn committed Aug 31, 2023
1 parent eb14f9c commit 9c09351
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 10 deletions.
32 changes: 23 additions & 9 deletions impl/src/main/java/org/jboss/weld/invokable/InvokerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,26 @@ private MethodHandle createMethodHandleFromTransformer(TransformerMetadata trans
} else if (transformer.isOutputTransformer() && result.type().parameterCount() > 0
&& !result.type().parameterType(0).equals(transformationArgType)) {
result = result.asType(result.type().changeParameterType(0, transformationArgType));
} else if (TransformerType.EXCEPTION.equals(transformer.getType()) && !result.type().returnType().equals(transformationArgType)) {
// exception handlers can return a subtype of original class and that should still be OK
result = result.asType(result.type().changeReturnType(this.method.getReturnType()));
} else if (TransformerType.EXCEPTION.equals(transformer.getType())) {
// if assignable, then just alter return type
if (this.method.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(this.method.getReturnType()));
} else {
// if not assignable, then we need to apply a return value transformer which hides the value in an exception
try {
MethodHandle hideReturnValue = getMethodHandle(ValueCarryingException.class.getDeclaredMethod("hideReturnValue", Object.class), Object.class);
// cast return value of the custom method we use to whatever the original method expects - we'll never use it anyway
hideReturnValue = hideReturnValue.asType(hideReturnValue.type().changeReturnType(this.method.getReturnType()));
// adapt the parameter type as well, we don't really care what it is, we just store it and throw it
hideReturnValue = hideReturnValue.asType(hideReturnValue.type().changeParameterType(0, result.type().returnType()));
result = MethodHandles.filterReturnValue(result, hideReturnValue);
} catch (NoSuchMethodException e) {
// should never happen
throw new IllegalStateException("Unable to locate Weld internal helper method");
}
}
}
return result;
}
Expand Down Expand Up @@ -182,12 +199,6 @@ private void processMethodCandidate(Method m, TransformerMetadata transformer, C
// TODO better exception
throw new DeploymentException("Output transformer " + transformer + " parameter is not assignable to the expected type " + transformationArgType);
}
// TODO this isn't currently defined in the API proposal but it looks like it's a limitation of method handles
if (TransformerType.EXCEPTION.equals(transformer.getType())
&& !method.getReturnType().isAssignableFrom(m.getReturnType())) {
// TODO better exception
throw new DeploymentException("Exception transformer return type must be equal to or a subclass of the original method return type! Transformer: " + transformer);
}
methodHandleArgs.add(m.getParameters()[0].getType());
}
} else {
Expand Down Expand Up @@ -280,6 +291,9 @@ public R invoke(T instance, Object[] arguments) {
try {
// TODO the need to cast is weird, shouldn't this method just return Object, assuming user knows?
return (R) beanMethodHandle.invokeWithArguments(methodArgs);
} catch (ValueCarryingException e) {
// there was an exception transformer which we wrapped, just return what was inside
return (R) e.getMethodReturnValue();
} catch (Throwable e) {
// TODO at this point there might have been exception transformer invoked as well, guess we just rethrow?
// we just rethrow the original exception
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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 hideReturnValue(Object methodRetType) throws ValueCarryingException {
// rethrow method return type inside exception
throw new ValueCarryingException(methodRetType);
}


private final Object methodReturnValue;
public ValueCarryingException(Object methodReturnValue) {
this.methodReturnValue = methodReturnValue;
}

public Object getMethodReturnValue() {
return methodReturnValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public class ObservingExtension implements Extension {
Invoker<ActualBean, ?> transformReturnType2;
Invoker<ExceptionalBean, ?> transformException1;
Invoker<ExceptionalBean, ?> transformException2;
Invoker<ExceptionalBean, ?> transformException3;

public Invoker<ActualBean, ?> getTransformReturnType1() {
return transformReturnType1;
Expand All @@ -32,6 +33,9 @@ public class ObservingExtension implements Extension {
public Invoker<ExceptionalBean, ?> getTransformException2() {
return transformException2;
}
public Invoker<ExceptionalBean, ?> getTransformException3() {
return transformException3;
}

public Invoker<ActualBean, ?> getNoTransformer() {
return noTransformer;
Expand Down Expand Up @@ -61,5 +65,8 @@ public void observeExceptionally(@Observes ProcessManagedBean<ExceptionalBean> p
transformException2 = pmb.createInvoker(invokableMethod)
.setExceptionTransformer(Transformer.class, "transformException2")
.build();
transformException3 = pmb.createInvoker(invokableMethod)
.setExceptionTransformer(Transformer.class, "transformException3")
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,20 @@ public void testReturnTypeTransformerAssignability() {

@Test
public void testExceptionTransformerAssignability() {
// apply transformers, first one swallows exception and returns Beta, the other just String
// 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ public static Beta transformException1(Throwable t) {
public static Gamma transformException2(Throwable t) {
return new Gamma(42, 42);
}

// return type is completely different
public static String transformException3(Throwable t) {
return "foobar";
}
}

0 comments on commit 9c09351

Please sign in to comment.