Skip to content

Commit

Permalink
@TestTransaction - move logic to the TestTransactionInterceptor class
Browse files Browse the repository at this point in the history
- add basic support for interceptor's inheritance
- other unsupported use cases are described here:
quarkusio#11942
  • Loading branch information
mkouba committed Sep 7, 2020
1 parent 84d816c commit a2d057f
Show file tree
Hide file tree
Showing 9 changed files with 190 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,11 @@

import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;

import java.lang.reflect.Modifier;
import java.util.Properties;

import javax.annotation.Priority;
import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;
import javax.transaction.TransactionScoped;
import javax.transaction.UserTransaction;

import com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean;
import com.arjuna.ats.internal.arjuna.coordinator.CheckedActionFactoryImple;
Expand All @@ -24,30 +19,22 @@

import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.ContextRegistrarBuildItem;
<<<<<<< HEAD
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
=======
import io.quarkus.arc.deployment.GeneratedBeanBuildItem;
import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor;
>>>>>>> Add the @TestTransaction annotation
import io.quarkus.arc.deployment.UnremovableBeanBuildItem;
import io.quarkus.arc.processor.ContextRegistrar;
import io.quarkus.deployment.Capability;
import io.quarkus.deployment.Feature;
import io.quarkus.deployment.IsTest;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.CapabilityBuildItem;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageSystemPropertyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem;
import io.quarkus.gizmo.ClassCreator;
import io.quarkus.gizmo.FieldCreator;
import io.quarkus.gizmo.FieldDescriptor;
import io.quarkus.gizmo.MethodCreator;
import io.quarkus.gizmo.MethodDescriptor;
import io.quarkus.gizmo.ResultHandle;
import io.quarkus.narayana.jta.runtime.CDIDelegatingTransactionManager;
import io.quarkus.narayana.jta.runtime.NarayanaJtaProducers;
import io.quarkus.narayana.jta.runtime.NarayanaJtaRecorder;
Expand All @@ -61,10 +48,11 @@
import io.quarkus.narayana.jta.runtime.interceptor.TransactionalInterceptorRequiresNew;
import io.quarkus.narayana.jta.runtime.interceptor.TransactionalInterceptorSupports;
import io.smallrye.context.jta.context.propagation.JtaContextProvider;
import io.quarkus.runtime.LaunchMode;

class NarayanaJtaProcessor {

private static final String TEST_TRANSACTION = "io.quarkus.test.TestTransaction";

@BuildStep
public NativeImageSystemPropertyBuildItem nativeImageSystemPropertyBuildItem() {
return new NativeImageSystemPropertyBuildItem("CoordinatorEnvironmentBean.transactionStatusManagerEnable", "false");
Expand Down Expand Up @@ -119,38 +107,22 @@ public void build(NarayanaJtaRecorder recorder,
recorder.setDefaultTimeout(transactions);
}

@BuildStep
void testTx(LaunchModeBuildItem lm, BuildProducer<GeneratedBeanBuildItem> generatedBeanBuildItemBuildProducer) {
if (lm.getLaunchMode() != LaunchMode.TEST) {
return;
}
@BuildStep(onlyIf = IsTest.class)
void testTx(BuildProducer<GeneratedBeanBuildItem> generatedBeanBuildItemBuildProducer,
BuildProducer<AdditionalBeanBuildItem> additionalBeans) {
//generate the annotated interceptor with gizmo
//all the logic is in the parent, but we don't have access to the
//binding annotation here
try (ClassCreator c = new ClassCreator(new GeneratedBeanGizmoAdaptor(generatedBeanBuildItemBuildProducer),
TestTransactionInterceptor.class.getName() + "Generated", null, TestTransactionInterceptor.class.getName())) {
c.addAnnotation("io.quarkus.test.TestTransaction");
try (ClassCreator c = ClassCreator.builder()
.classOutput(new GeneratedBeanGizmoAdaptor(generatedBeanBuildItemBuildProducer)).className(
TestTransactionInterceptor.class.getName() + "Generated")
.superClass(TestTransactionInterceptor.class).build()) {
c.addAnnotation(TEST_TRANSACTION);
c.addAnnotation(Interceptor.class.getName());
c.addAnnotation(Priority.class).addValue("value", Interceptor.Priority.PLATFORM_BEFORE + 200);

FieldCreator field = c.getFieldCreator("ut", UserTransaction.class);
field.setModifiers(Modifier.PUBLIC);
field
.addAnnotation(Inject.class);

MethodCreator m = c.getMethodCreator("work", Object.class, InvocationContext.class);
m.addAnnotation(AroundInvoke.class);
m.addException(Exception.class);

ResultHandle ut = m.readInstanceField(FieldDescriptor.of(c.getClassName(), "ut", UserTransaction.class),
m.getThis());
ResultHandle result = m
.invokeStaticMethod(MethodDescriptor.ofMethod(TestTransactionInterceptor.class, "intercept", Object.class,
UserTransaction.class, InvocationContext.class), ut, m.getMethodParam(0));

m.returnValue(result);
}

additionalBeans.produce(AdditionalBeanBuildItem.builder().addBeanClass(TestTransactionInterceptor.class)
.addBeanClass(TEST_TRANSACTION).build());
}

@BuildStep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import java.util.List;
import java.util.ServiceLoader;

import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
import javax.transaction.UserTransaction;

Expand All @@ -21,7 +23,11 @@ public class TestTransactionInterceptor {
CALLBACKS = callbacks;
}

public static Object intercept(UserTransaction userTransaction, InvocationContext context) throws Exception {
@Inject
UserTransaction userTransaction;

@AroundInvoke
public Object intercept(InvocationContext context) throws Exception {
try {
userTransaction.begin();
for (TestTransactionCallback i : CALLBACKS) {
Expand All @@ -35,7 +41,6 @@ public static Object intercept(UserTransaction userTransaction, InvocationContex
} finally {
userTransaction.rollback();
}

}

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package io.quarkus.arc.processor;

import static io.quarkus.arc.processor.IndexClassLookupUtils.getClassByName;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.enterprise.inject.spi.InterceptionType;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.AnnotationTarget;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.MethodInfo;
import org.jboss.jandex.Type;
Expand Down Expand Up @@ -47,39 +52,56 @@ public class InterceptorInfo extends BeanInfo implements Comparable<InterceptorI
null, null, null, Collections.emptyList(), null, false);
this.bindings = bindings;
this.priority = priority;
MethodInfo aroundInvoke = null;
MethodInfo aroundConstruct = null;
MethodInfo postConstruct = null;
MethodInfo preDestroy = null;
for (MethodInfo method : target.asClass().methods()) {
if (aroundInvoke == null && method.hasAnnotation(DotNames.AROUND_INVOKE)) {
aroundInvoke = method;
} else if (method.hasAnnotation(DotNames.AROUND_CONSTRUCT)) {
// validate compliance with rules for AroundConstruct methods
if (!method.parameters().equals(Collections.singletonList(
Type.create(DotName.createSimple("javax.interceptor.InvocationContext"), Type.Kind.CLASS)))) {
throw new IllegalStateException(
"@AroundConstruct must have exactly one argument of type javax.interceptor.InvocationContext, but method "
+ method.asMethod() + " declared by " + method.declaringClass()
+ " violates this.");
List<MethodInfo> aroundInvokes = new ArrayList<>();
List<MethodInfo> aroundConstructs = new ArrayList<>();
List<MethodInfo> postConstructs = new ArrayList<>();
List<MethodInfo> preDestroys = new ArrayList<>();

ClassInfo aClass = target.asClass();
Set<DotName> scanned = new HashSet<>();
while (aClass != null) {
if (!scanned.add(aClass.name())) {
continue;
}
for (MethodInfo method : aClass.methods()) {
if (Modifier.isStatic(method.flags())) {
continue;
}
if (!method.returnType().kind().equals(Type.Kind.VOID) &&
!method.returnType().name().equals(DotNames.OBJECT)) {
throw new IllegalStateException("Return type of @AroundConstruct method must be Object or void, but method "
+ method.asMethod() + " declared by " + method.declaringClass()
+ " violates this.");
if (method.hasAnnotation(DotNames.AROUND_INVOKE)) {
aroundInvokes.add(method);
} else if (method.hasAnnotation(DotNames.AROUND_CONSTRUCT)) {
// validate compliance with rules for AroundConstruct methods
if (!method.parameters().equals(Collections.singletonList(
Type.create(DotName.createSimple("javax.interceptor.InvocationContext"), Type.Kind.CLASS)))) {
throw new IllegalStateException(
"@AroundConstruct must have exactly one argument of type javax.interceptor.InvocationContext, but method "
+ method.asMethod() + " declared by " + method.declaringClass()
+ " violates this.");
}
if (!method.returnType().kind().equals(Type.Kind.VOID) &&
!method.returnType().name().equals(DotNames.OBJECT)) {
throw new IllegalStateException(
"Return type of @AroundConstruct method must be Object or void, but method "
+ method.asMethod() + " declared by " + method.declaringClass()
+ " violates this.");
}
aroundConstructs.add(method);
} else if (method.hasAnnotation(DotNames.POST_CONSTRUCT)) {
postConstructs.add(method);
} else if (method.hasAnnotation(DotNames.PRE_DESTROY)) {
preDestroys.add(method);
}
aroundConstruct = method;
} else if (postConstruct == null && method.hasAnnotation(DotNames.POST_CONSTRUCT)) {
postConstruct = method;
} else if (preDestroy == null && method.hasAnnotation(DotNames.PRE_DESTROY)) {
preDestroy = method;
}

DotName superTypeName = aClass.superName();
aClass = superTypeName == null || DotNames.OBJECT.equals(superTypeName) ? null
: getClassByName(beanDeployment.getIndex(), superTypeName);
}
this.aroundInvoke = aroundInvoke;
this.aroundConstruct = aroundConstruct;
this.postConstruct = postConstruct;
this.preDestroy = preDestroy;

this.aroundInvoke = aroundInvokes.isEmpty() ? null : aroundInvokes.get(0);
this.aroundConstruct = aroundConstructs.isEmpty() ? null : aroundConstructs.get(0);
this.postConstruct = postConstructs.isEmpty() ? null : postConstructs.get(0);
this.preDestroy = preDestroys.isEmpty() ? null : preDestroys.get(0);
if (aroundConstruct == null && aroundInvoke == null && preDestroy == null && postConstruct == null) {
LOGGER.warnf("%s declares no around-invoke method nor a lifecycle callback!", this);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.quarkus.arc.test.interceptors.inheritance;

import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

@One
@Interceptor
public class Interceptor1 extends OverridenInterceptor {

@AroundInvoke
@Override
public Object intercept(InvocationContext ctx) throws Exception {
return ctx.proceed() + "1";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.quarkus.arc.test.interceptors.inheritance;

import javax.interceptor.Interceptor;

@Two
@Interceptor
public class Interceptor2 extends OverridenInterceptor {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package io.quarkus.arc.test.interceptors.inheritance;

import static org.junit.jupiter.api.Assertions.assertEquals;

import io.quarkus.arc.Arc;
import io.quarkus.arc.test.ArcTestContainer;
import javax.enterprise.context.ApplicationScoped;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

/**
* "Around-invoke methods may be defined on interceptor classes and/or the target class and/or super-
* classes of the target class or the interceptor classes. However, only one around-invoke method may be
* defined on a given class."
*/
public class InterceptorSuperclassTest {

@RegisterExtension
public ArcTestContainer container = new ArcTestContainer(Interceptor1.class, Interceptor2.class, One.class, Two.class,
OverridenInterceptor.class, Fool.class);

@Test
public void testInterception() {
Fool fool = Arc.container().instance(Fool.class).get();
assertEquals("ping1", fool.pingOne());
assertEquals("pingoverriden", fool.pingTwo());
}

@ApplicationScoped
public static class Fool {

@One
String pingOne() {
return "ping";
}

@Two
String pingTwo() {
return "ping";
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.quarkus.arc.test.interceptors.inheritance;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;

@Target({ TYPE, METHOD })
@Retention(RUNTIME)
@Documented
@InterceptorBinding
public @interface One {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.quarkus.arc.test.interceptors.inheritance;

import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

public class OverridenInterceptor {

@AroundInvoke
public Object intercept(InvocationContext ctx) throws Exception {
return ctx.proceed() + "overriden";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.quarkus.arc.test.interceptors.inheritance;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import javax.interceptor.InterceptorBinding;

@Target({ TYPE, METHOD })
@Retention(RUNTIME)
@Documented
@InterceptorBinding
public @interface Two {

}

0 comments on commit a2d057f

Please sign in to comment.