payara / Payara

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.
http://www.payara.fish
Other
879 stars 300 forks source link

FISH-8309: Persistence Context is NULL on CDI Injected beans into MDB and tck fix #6744

Closed breakponchito closed 2 months ago

breakponchito commented 2 months ago

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.

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:

[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:

@MessageDriven(name = "DemoJmsTopicReceiverImpl", activationConfig = { //
        @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "jms/PayaraDemoJmsTopic"), //
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "jakarta.jms.Topic"), //
        @ActivationConfigProperty(propertyName = "resourceAdapter", propertyValue = "jmsra") //
})

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:

image

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

breakponchito commented 2 months ago

Jenkins test please