Payara Server is an open source middleware platform that supports reliable and secure deployments of Java EE (Jakarta EE) and MicroProfile applications in any environment: on premise, in the cloud or hybrid.
Fix for Persistence Context injection issue on CDI beans and TCK fix for EJB Remote tests
Description
This PR addresses an issue where a PersistenceContext injected into a CDI bean, subsequently injected into a Message-Driven Bean (MDB) or a Stateless EJB, resolves to null. The problem occurs due to the different contexts in which the EntityManager Resource Descriptor instances are added during processing.
Context
The issue arises when the EntityManager Resource Descriptor instance added to the CDI bean's EjbBundleDescriptor is not accessed from the MDB bean's EjbMessageBeanDescriptor. Although EjbBundleDescriptor is the parent descriptor context, it is not the superclass of EjbMessageBeanDescriptor. As a result, when accessing the EntityManager Resource Descriptor from EjbMessageBeanDescriptor, it does not look up in the EjbBundleDescriptor context, leading to a null resolution of the PersistenceContext.
An instance of a PersistenceContext injected into a CDI bean that is then injected into a Message-Driven bean, incorrectly resolves to NULL.
@RequestScoped
@Transactional(Transactional.TxType.SUPPORTS)
public class DemoCdiServiceImpl implements DemoCdiService {
@PersistenceContext(unitName = "payara-demoapp")
private EntityManager entityManager;
@Override
public String getDemoData() {
final DemoDataBE be = this.entityManager.find(DemoDataBE.class, 1L); //This will trigger a `NullPointerException`
if (be == null) {
return null;
}
return be.getDescTest();
}
}
@MessageDriven(name = "DemoJmsTopicReceiverImpl", activationConfig = {
@ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "jms/PayaraDemoJmsTopic"),
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "jakarta.jms.Topic"),
@ActivationConfigProperty(propertyName = "resourceAdapter", propertyValue = "jmsra")
})
public class DemoJmsTopicReceiverImpl implements MessageListener {
@Inject
DemoCdiService service;
@Override
public void onMessage(final Message message) {
try {
this.service.saveDemoData("FROM JMS: " + messageString); //But only when called via this injection.
} catch (final JMSException e) {
throw new RuntimeException("demo does not handle exceptions", e);
}
}
}
The injection of a @PersistenceContext should work in this case, as the injection of CDI beans is fully supported in Message Driven Beans.
curl -H "Content-Type: text/plain" -X POST http://localhost:8080/payara-bug-jms/api/data/createJms -d "This is a sample message"
The request will be completed successfully, but the following error stack trace (and additional details) will be printed out to the server log:
[2024-02-05T15:43:00.689-0500] [Payara 5.57.0] [SEVERE] [] [fmg.lz06.eapps.payarafull.demoapp.ejb.DemoJmsTopicReceiverImpl] [tid: _ThreadID=236 _ThreadName=orb-thread-pool-1 (pool #1): worker-2] [timeMillis: 1707165780689] [levelValue: 1000] [[
EJB has entitymanager: true]]
[2024-02-05T15:43:00.690-0500] [Payara 5.57.0] [INFO] [] [fmg.lz06.eapps.payarafull.demoapp.business.DemoCdiServiceImpl] [tid: _ThreadID=236 _ThreadName=orb-thread-pool-1 (pool #1): worker-2] [timeMillis: 1707165780690] [levelValue: 800] [[
Calling demo data method save]]
[2024-02-05T15:43:00.691-0500] [Payara 5.57.0] [SEVERE] [] [fmg.lz06.eapps.payarafull.demoapp.business.DemoCdiServiceImpl] [tid: _ThreadID=236 _ThreadName=orb-thread-pool-1 (pool #1): worker-2] [timeMillis: 1707165780691] [levelValue: 1000] [[
CDI has entitymanager: false]]
[2024-02-05T15:43:00.692-0500] [Payara 5.57.0] [WARNING] [] [jakarta.resourceadapter.mqjmsra.inbound.message] [tid: _ThreadID=236 _ThreadName=orb-thread-pool-1 (pool #1): worker-2] [timeMillis: 1707165780692] [levelValue: 900] [[
MQJMSRA_MR2001: run:Caught Exception from onMessage():Redelivering:
jakarta.ejb.EJBException: message-driven bean method public abstract void jakarta.jms.MessageListener.onMessage(jakarta.jms.Message) system exception
at org.glassfish.ejb.mdb.MessageBeanContainer.deliverMessage(MessageBeanContainer.java:1250)
at org.glassfish.ejb.mdb.MessageBeanListenerImpl.deliverMessage(MessageBeanListenerImpl.java:131)
at com.sun.enterprise.connectors.inbound.MessageEndpointInvocationHandler.invoke(MessageEndpointInvocationHandler.java:171)
at com.sun.proxy.$Proxy363.onMessage(Unknown Source)
at com.sun.messaging.jms.ra.OnMessageRunner.run(OnMessageRunner.java:253)
at com.sun.enterprise.connectors.work.OneWork.doWork(OneWork.java:108)
at com.sun.corba.ee.impl.threadpool.ThreadPoolImpl$TaskRunner.run(ThreadPoolImpl.java:189)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.NullPointerException
at fmg.lz06.eapps.payarafull.demoapp.business.DemoCdiServiceImpl.saveDemoData(DemoCdiServiceImpl.java:57)
at fmg.lz06.eapps.payarafull.demoapp.business.DemoCdiServiceImpl$Proxy$_$$_WeldSubclass.saveDemoData$$super(Unknown Source)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.jboss.weld.interceptor.proxy.TerminalAroundInvokeInvocationContext.proceedInternal(TerminalAroundInvokeInvocationContext.java:51)
at org.jboss.weld.interceptor.proxy.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:78)
at org.glassfish.jersey.ext.cdi1x.transaction.internal.WebAppExceptionInterceptor.intercept(WebAppExceptionInterceptor.java:53)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.jboss.weld.interceptor.reader.SimpleInterceptorInvocation$SimpleMethodInvocation.invoke(SimpleInterceptorInvocation.java:73)
at org.jboss.weld.interceptor.proxy.NonTerminalAroundInvokeInvocationContext.proceedInternal(NonTerminalAroundInvokeInvocationContext.java:66)
at org.jboss.weld.interceptor.proxy.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:78)
at org.glassfish.cdi.transaction.TransactionalInterceptorBase.proceed(TransactionalInterceptorBase.java:212)
at org.glassfish.cdi.transaction.TransactionalInterceptorRequired.transactional(TransactionalInterceptorRequired.java:115)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.jboss.weld.interceptor.reader.SimpleInterceptorInvocation$SimpleMethodInvocation.invoke(SimpleInterceptorInvocation.java:73)
at org.jboss.weld.interceptor.proxy.InterceptorMethodHandler.executeAroundInvoke(InterceptorMethodHandler.java:84)
at org.jboss.weld.interceptor.proxy.InterceptorMethodHandler.executeInterception(InterceptorMethodHandler.java:72)
at org.jboss.weld.interceptor.proxy.InterceptorMethodHandler.invoke(InterceptorMethodHandler.java:56)
at org.jboss.weld.bean.proxy.CombinedInterceptorAndDecoratorStackMethodHandler.invoke(CombinedInterceptorAndDecoratorStackMethodHandler.java:79)
at org.jboss.weld.bean.proxy.CombinedInterceptorAndDecoratorStackMethodHandler.invoke(CombinedInterceptorAndDecoratorStackMethodHandler.java:68)
at fmg.lz06.eapps.payarafull.demoapp.business.DemoCdiServiceImpl$Proxy$_$$_WeldSubclass.saveDemoData(Unknown Source)
at fmg.lz06.eapps.payarafull.demoapp.business.DemoCdiServiceImpl$Proxy$_$$_WeldClientProxy.saveDemoData(Unknown Source)
at fmg.lz06.eapps.payarafull.demoapp.ejb.DemoJmsTopicReceiverImpl.onMessage(DemoJmsTopicReceiverImpl.java:41)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.glassfish.ejb.security.application.EJBSecurityManager.runMethod(EJBSecurityManager.java:588)
at org.glassfish.ejb.security.application.EJBSecurityManager.invoke(EJBSecurityManager.java:408)
at com.sun.ejb.containers.BaseContainer.invokeBeanMethod(BaseContainer.java:4835)
at com.sun.ejb.EjbInvocation.invokeBeanMethod(EjbInvocation.java:665)
at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:834)
at com.sun.ejb.EjbInvocation.proceed(EjbInvocation.java:615)
at org.jboss.weld.module.ejb.AbstractEJBRequestScopeActivationInterceptor.aroundInvoke(AbstractEJBRequestScopeActivationInterceptor.java:81)
at org.jboss.weld.module.ejb.SessionBeanInterceptor.aroundInvoke(SessionBeanInterceptor.java:52)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at com.sun.ejb.containers.interceptors.AroundInvokeInterceptor.intercept(InterceptorManager.java:888)
at com.sun.ejb.containers.interceptors.AroundInvokeChainImpl.invokeNext(InterceptorManager.java:833)
at com.sun.ejb.containers.interceptors.InterceptorManager.intercept(InterceptorManager.java:375)
at com.sun.ejb.containers.BaseContainer.__intercept(BaseContainer.java:4807)
at com.sun.ejb.containers.BaseContainer.intercept(BaseContainer.java:4795)
at org.glassfish.ejb.mdb.MessageBeanContainer.deliverMessage(MessageBeanContainer.java:1215)
... 11 more
]]
Expected Outcome
An instance of a PersistenceContext should not be null when injected on a CDI bean that is injected into an MDB. The reproducer code tests the injection of a persistence context directly in the Message Driven Bean and on the CDI bean when used directly by an HTTP resource.
The same scenario tested on Payara Server 6 fails with the same error.
You can test the reproducer on Payara Server 6 using the same application, but you’ll have to modify the Message Driven Bean configuration to reference a jakarta.jms.Topic instead:
Use this same principle for creating the topic on the server.
Solution
To resolve this issue, the EntityManager Resource Descriptor instance added to the CDI bean's EjbBundleDescriptor context should also be accessible from the MDB bean's EjbMessageBeanDescriptor context. This can be achieved by ensuring that processing is done in a way that EntityManager Resource Descriptors are looked up from child contexts to parent context.
Blockers
Testing
New tests
Testing Performed
Manual testing following steps to reproduce and seeing the following messages on console:
and passing EJB TCK from CDI
[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 13.974 s - in TestSuite [INFO] [INFO] Results: [INFO] [INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0
and the full execution:
[INFO] [mvn.test] [INFO] Tests run: 1831, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 960.949 s - in TestSuite [INFO] [mvn.test] [INFO] [INFO] [mvn.test] [INFO] Results: [INFO] [mvn.test] [INFO] [INFO] [mvn.test] [INFO] Tests run: 1831, Failures: 0, Errors: 0, Skipped: 0 [INFO] [mvn.test] [INFO] [INFO] [mvn.test] [INFO] [INFO] [mvn.test] [INFO] --- maven-surefire-report-plugin:3.0.0-M5:report-only (generate-test-report) @ weld-payara-runner-tck --- [INFO] [mvn.test] [WARNING] Unable to locate Test Source XRef to link to - DISABLED
Testing Environment
windows 11, Azul JDK 11, Maven 3.9.5
Ubuntu Linux 20.04, Azul JDK 11, Maven 3.8.6
Fix for Persistence Context injection issue on CDI beans and TCK fix for EJB Remote tests
Description
This PR addresses an issue where a PersistenceContext injected into a CDI bean, subsequently injected into a Message-Driven Bean (MDB) or a Stateless EJB, resolves to null. The problem occurs due to the different contexts in which the EntityManager Resource Descriptor instances are added during processing.
Context
The issue arises when the EntityManager Resource Descriptor instance added to the CDI bean's EjbBundleDescriptor is not accessed from the MDB bean's EjbMessageBeanDescriptor. Although EjbBundleDescriptor is the parent descriptor context, it is not the superclass of EjbMessageBeanDescriptor. As a result, when accessing the EntityManager Resource Descriptor from EjbMessageBeanDescriptor, it does not look up in the EjbBundleDescriptor context, leading to a null resolution of the PersistenceContext.
An instance of a PersistenceContext injected into a CDI bean that is then injected into a Message-Driven bean, incorrectly resolves to NULL.
The injection of a
@PersistenceContext
should work in this case, as the injection of CDI beans is fully supported in Message Driven Beans.Testing
Steps to Reproduce
1 - Download the attached project and build it. 3598-payara-jms-inject-bug-reproducerPayara6.zip
mvn clean install
2 - Start a new Payara Server domain (the default domain works well)
asadmin start-domain domain1
3 - Create a new JMS Topic with the following command:
asadmin create-jms-resource --resType=jakarta.jms.Topic --property=Name=DemoTopic jms/PayaraDemoJmsTopic
4 - Deploy the EAR artefact from the project build in the server’s DAS:
asadmin deploy payara-jms-inject-bug-reproducer-ear/target/payara-jms-inject-bug-reproducer-ear-0.0.1-SNAPSHOT.ear
5 - Fire this sample HTTP request:
curl -H "Content-Type: text/plain" -X POST http://localhost:8080/payara-bug-jms/api/data/createJms -d "This is a sample message"
The request will be completed successfully, but the following error stack trace (and additional details) will be printed out to the server log:
Expected Outcome
An instance of a
PersistenceContext
should not be null when injected on a CDI bean that is injected into an MDB. The reproducer code tests the injection of a persistence context directly in the Message Driven Bean and on the CDI bean when used directly by an HTTP resource.The same scenario tested on Payara Server 6 fails with the same error.
You can test the reproducer on Payara Server 6 using the same application, but you’ll have to modify the Message Driven Bean configuration to reference a jakarta.jms.Topic instead:
Use this same principle for creating the topic on the server.
Solution
To resolve this issue, the EntityManager Resource Descriptor instance added to the CDI bean's
EjbBundleDescriptor
context should also be accessible from the MDB bean'sEjbMessageBeanDescriptor
context. This can be achieved by ensuring that processing is done in a way thatEntityManager
Resource Descriptors are looked up from child contexts to parent context.Blockers
Testing
New tests
Testing Performed
Manual testing following steps to reproduce and seeing the following messages on console:
and passing EJB TCK from CDI
[INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 13.974 s - in TestSuite [INFO] [INFO] Results: [INFO] [INFO] Tests run: 9, Failures: 0, Errors: 0, Skipped: 0
and the full execution:
[INFO] [mvn.test] [INFO] Tests run: 1831, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 960.949 s - in TestSuite [INFO] [mvn.test] [INFO] [INFO] [mvn.test] [INFO] Results: [INFO] [mvn.test] [INFO] [INFO] [mvn.test] [INFO] Tests run: 1831, Failures: 0, Errors: 0, Skipped: 0 [INFO] [mvn.test] [INFO] [INFO] [mvn.test] [INFO] [INFO] [mvn.test] [INFO] --- maven-surefire-report-plugin:3.0.0-M5:report-only (generate-test-report) @ weld-payara-runner-tck --- [INFO] [mvn.test] [WARNING] Unable to locate Test Source XRef to link to - DISABLED
Testing Environment
windows 11, Azul JDK 11, Maven 3.9.5 Ubuntu Linux 20.04, Azul JDK 11, Maven 3.8.6
Documentation
Notes for Reviewers