(written by dan.j.allen)
I was recently notified of a possible bug in Weld. I decided to give Arquillian a try to prove whether or not the bug was real.
I'll start with the problem. A developer reported that Java EE resource injections were not being applied to a bean with at least one active CDI interceptor. The developer was invoking the bean from EL, so there was a chance this only happened when the bean was looked up by name. We'll cover all bases.
First, create a project using the minimal Java EE Weld archetype.
Next, let's define two managed beans, one without any interceptors (NativeBean) and one with an interceptor (InterceptedBean).
@Model public class NativeBean { @PersistenceContext(unitName="em") private EntityManager em; public EntityManager getEm() { return em; } } @Model public class InterceptedBean { @PersistenceContext(unitName = "em") private EntityManager em; @Inject private NativeBean nativeBean; @Transactional public EntityManager getEm() { return em; } @Transactional public EntityManager getEmFromNativeBean() { return nativeBean != null ? nativeBean.getEm() : null; } }
We'll also need an interceptor (TransactionIntereceptor) and an interceptor binding annotation (Transactional).
@Transactional @Interceptor public class TransactionInterceptor { @Inject private UserTransaction tx; @AroundInvoke public Object workInTransaction(InvocationContext invocation) throws Exception { try { tx.begin(); Object result = invocation.proceed(); tx.commit(); return result; } catch (Exception e) { tx.rollback(); throw e; } } } @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) @InterceptorBinding @Documented @Inherited public @interface Transactional {}
Now we need to add Arquillian to our project. That involves the following steps:
1. Add the Arquillian JUnit or TestNG test-scoped dependency. We'll use TestNG:
<dependency> <groupId>org.jboss.arquillian</groupId> <artifactId>arquillian-testng</artifactId> <version>1.0.0.Alpha1</version> <scope>test</scope> </dependency>
2. Add a profile for a Java EE container. We'll use JBoss AS 6. We should execute our tests against JBoss AS 6.0.0.M1 and 6.0.0.M2 (and GlassFish V3) to see if there was a recent fix in Weld that corrects the bug.
<profiles> <profile> <id>jbossas-remote-60</id> <dependencies> <dependency> <groupId>org.jboss.arquillian.container</groupId> <artifactId>arquillian-jbossas-remote-60</artifactId> <version>1.0.0.Alpha1</version> </dependency> </dependencies> </profile> </profiles>
3. Add jndi.properties to src/test/resources
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces java.naming.provider.url=jnp://localhost:1099
We're almost ready to write our test. But since we need to evaluate EL, we'll need to throw in some classes for bootstrapping EL in our test case. You can find details in the thread
.
One thing to note, you'll need to be an the el-impl JAR in server/default/lib because the bootstrap code references a couple of implementation classes.
Okay, we are ready to write our test.
@Test(groups = "javaee") public class ResourceInjectionBugTest extends Arquillian { @Deployment public static Archive<?> createDeployment() { return Archives.create("test.jar", JavaArchive.class) .addPackage(Transactional.class.getPackage()) .addPackage(Expressions.class.getPackage()) .addManifestResource("test-beans.xml", ArchivePaths.create("beans.xml")) .addManifestResource("test-persistence.xml", ArchivePaths.create("persistence.xml")); } @Inject Instance<NativeBean> nativeBean; @Inject Instance<InterceptedBean> interceptedBean; @Inject Expressions expressions; @Test public void testResourceInjectionOnTypeSafeLookup() { assertNotNull(nativeBean.get().getEm()); assertNotNull(interceptedBean.get().getEm()); } @Test public void testResourceInjectionOnNameLookup() { assertNotNull(expressions.evaluateValueExpression("#{nativeBean.em}")); assertNotNull(expressions.evaluateValueExpression("#{interceptedBean.em}")); assertNotNull(expressions.evaluateValueExpression("#{interceptedBean.emFromNativeBean}")); } }
You'll notice that we are looking up our beans in both a type-safe and non-type-safe way and seeing if the EntityManager gets injected with and without the interceptor.
The interceptor is activated in the test-beans.xml file:
<beans> <interceptors> <class>org.jboss.weld.resourceinjectionbug.TransactionInterceptor</class> </interceptors> </beans>
For completeness, I'll include the contents of src/test/resources/test-persistence.xml:
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"> <persistence-unit name="em" transaction-type="JTA"> <!-- JBoss AS-specific datasource name --> <jta-data-source>java:/DefaultDS</jta-data-source> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/> </properties> </persistence-unit> </persistence>
To run the test, first start the JBoss AS that you want to test on. Then, execute the test as follows:
mvn test -Pjbossas-remote-60
Try this out on different versions of JBoss AS (with different versions of Weld) and see when this bug shows up. From this exercise, I learned that this bug is present in Weld 1.0, but has been fixed in the Weld 1.0.1 series (Weld 1.0.1-CR1 is included in JBoss AS 6.0.0.M2).
Another possible bug was reported in Weld (actually JBoss AS 6.0.0.M2, but Weld is involved) in this thread on seamframework.org. The developer reported that Weld is unable to locate no-interface EJB stateless session beans in JBoss AS 6.0.0.M2.
Once again, we turn to Arquillian.
Working with the setup we defined in the previous post, let's get right to the code and the test. First, we define two EJB stateless session beans, one with an interface (NoInterfaceSessionBean), and one without (FormalSessionBean):
public @Stateless @Named class NoInterfaceSessionBean { public boolean isFound() { return true; } } @Local public interface FormalSessionBeanLocal { boolean isFound(); } public @Stateless @Named class FormalSessionBean implements FormalSessionBeanLocal { @Override public boolean isFound() { return true; } }
Next, we create a test case that attempts to invoke the two beans. We'll inject the beans to test into the test class.
@Test(groups = "javaee") public class NoInterfaceEjbBugTest extends Arquillian { @Deployment public static Archive<?> createDeployment() { return Archives.create("test.jar", JavaArchive.class) .addPackage(NoInterfaceSessionBean.class.getPackage()) .addPackage(Expressions.class.getPackage()) .addManifestResource(new ByteArrayAsset(new byte[0]), ArchivePaths.create("beans.xml")); } @Inject Instance<FormalSessionBeanLocal> formalSessionBean; @Inject Instance<NoInterfaceSessionBean> noInterfaceSessionBean; @Inject Expressions expressions; @Test public void testSessionBeansOnTypeSafeLookup() { assertTrue(formalSessionBean.get().isFound()); assertTrue(noInterfaceSessionBean.get().isFound()); } @Test public void testSessionBeansOnNameLookup() { assertTrue( expressions.evaluateValueExpression("#{formalSessionBean.found}", Boolean.class)); assertTrue( expressions.evaluateValueExpression("#{noInterfaceSessionBean.found}", Boolean.class)); } }
Now start up JBoss AS 6.0.0.M2 and run the test:
mvn test -Pjbossas-remote-60 -Dtest=NoInterfaceEjbBugTest
You should see that the test fails. Unfortuantely, due to a bug with serialization in Arquillian, the reason for the failure is not being communicated back to the test runner. If you comment out the no-interface session bean calls, you'll see that the tests pass.
So indeed, this is a real bug!
This article was generated from the following thread: Using Arquillian to test a possible bug in Weld
Comments