Search code examples
jakarta-eemanaged-beanjava-ee-7

Java EE 7: invoke EJB method+transaction from "unmanaged" context


Please consider the following scenario:

1) One Singleton SchedulerService that among other things manages/creates a bunch of JobQueues

@Startup
@Singleton
public class SchedulerService
{
    @Inject
    private Instance<JobQueue> jobQueueInstance;

    ...

    public JobQueue addQueue(String name)
    {
         JobQueue q = jobQueueInstance.get();
         ....
         return q;
    }
}

2) Possibly several JobQueues who each manage/launch/follow-up their running/pending jobs:

public class JobQueue implements SchedulerListener
{
    @PersistenceContext(unitName = "...")
    private EntityManager entityManager;

    public void addJob(Job newJob)
    {
       .... entityManager.persist(newJob); ....
       newJob.addSchedulerListener(this);
    }

    ...

    public void deleteJob(Job j)
    {
        ....  entityManager.delete(j); ....
    }

    // part of SchedulerListener, invoked from Job's Thread
    @Override
    public void taskSucceeded(Job job)
    {
        deleteJob(job);
    }

    // part of SchedulerListener, invoked from Job's Thread
    @Override
    public void taskFailed(Job job)
    {
        deleteJob(job);
    }
}

Everything is working nicely, entityManager is @Injected correctly, and when invoking addJob() and deleteJob() from other managed beans the entities are correctly persisted/deleted.

Now for the actual Job execution I'm using Cron4j, which is not CDI aware. It launches new Threads and runs the actual Job in that Thread. When a job ends, it informs my JobQueue (who listens for Job termination events) via taskSucceeded/taskFailed methods.

Because these taskSucceeded/taskFailed methods are invoked from the Job threads (who are not "container managed"), I understandably get the following Exception:

4:46:27,032 ERROR [cob.scheduler.service.JobQueue] (cron4j::scheduler[DEFAULT]::task[442]) Job xxx failed: javax.persistence.TransactionRequiredException: WFLYJPA0060: Transaction is required to perform this operation (either use a transaction or extended persistence context)
    at org.jboss.as.jpa.container.AbstractEntityManager.transactionIsRequired(AbstractEntityManager.java:869) [wildfly-jpa-9.0.0.Alpha1.jar:9.0.0.Alpha1]
    at org.jboss.as.jpa.container.AbstractEntityManager.merge(AbstractEntityManager.java:567) [wildfly-jpa-9.0.0.Alpha1.jar:9.0.0.Alpha1]
    at cob.scheduler.service.JobQueue.deleteJob(JobQueue.java:287) [classes:]
    at cob.scheduler.service.JobQueue.deleteAndAdvance(JobQueue.java:241) [classes:]
    at cob.scheduler.service.JobQueue.taskSucceeded(JobQueue.java:226) [classes:]
    at it.sauronsoftware.cron4j.Scheduler.notifyTaskSucceeded(Scheduler.java:724) [classes:]
    at it.sauronsoftware.cron4j.TaskExecutor$Runner.run(TaskExecutor.java:500) [classes:]
    at java.lang.Thread.run(Thread.java:744) [rt.jar:1.7.0_45]

I am wondering what would be a good way to proceed. Basically I need to somehow get "back into the EE container", i.e. invoke a container managed method from outside the EE realm.

I've read about ManagedExecutorService but I'm not sure if this applies or how to use it.

Also I tried to @Inject private JobQueue self; and invoke self.deleteJob() instead of merely this.deleteJob(), but this generates the following Exception upon deployment:

org.jboss.weld.exceptions.DeploymentException: WELD-001443: Pseudo scoped bean has circular dependencies. Dependency path: 
  - Managed Bean [class cob.scheduler.service.JobQueue] with qualifiers [@Any @Default],
  - [BackedAnnotatedField] @Inject private cob.scheduler.service.JobQueue.self,
  - Managed Bean [class cob.scheduler.service.JobQueue] with qualifiers [@Any @Default]
    at org.jboss.weld.bootstrap.Validator.reallyValidatePseudoScopedBean(Validator.java:904)
    at org.jboss.weld.bootstrap.Validator.validatePseudoScopedInjectionPoint(Validator.java:946)
    at org.jboss.weld.bootstrap.Validator.reallyValidatePseudoScopedBean(Validator.java:913)
    at org.jboss.weld.bootstrap.Validator.validatePseudoScopedBean(Validator.java:890)
    at org.jboss.weld.bootstrap.Validator.validateGeneralBean(Validator.java:148)
    at org.jboss.weld.bootstrap.Validator.validateRIBean(Validator.java:165)
    at org.jboss.weld.bootstrap.Validator.validateBean(Validator.java:529)
    at org.jboss.weld.bootstrap.ConcurrentValidator$1.doWork(ConcurrentValidator.java:68)
    at org.jboss.weld.bootstrap.ConcurrentValidator$1.doWork(ConcurrentValidator.java:66)
    at org.jboss.weld.executor.IterativeWorkerTaskFactory$1.call(IterativeWorkerTaskFactory.java:60)
    at org.jboss.weld.executor.IterativeWorkerTaskFactory$1.call(IterativeWorkerTaskFactory.java:53)
    at java.util.concurrent.FutureTask.run(FutureTask.java:262) [rt.jar:1.7.0_45]
    ... 3 more

Note: I had everything working but with "resource local" transaction management. I figured it would be easy converting this to JTA, but alas.

Any pointers would be appreciated.


Solution

  • Turns out the solution was not so hard. Every day we learn. Yey!

    1. @Inject the SchedulerService into the JobQueue. This creates a circular depencency but oh well.
    2. In SchedulerService, make a proxy method deleteJob(JobQueue jq, Job j). All this does is invoke jq.deleteJob(j)
    3. From the "unmanaged" event callbacks, invoke the proxy schedulerService.deleteJob(this, j) instead of this.deleteJob(j). By going over the @Injected proxy, Java EE kicks in again, creating transactions for us and all other wonderfull magic.
    4. Profit!