Please consider the following scenario:
1) One Singleton SchedulerService that among other things manages/creates a bunch of JobQueue
s
@Startup
@Singleton
public class SchedulerService
{
@Inject
private Instance<JobQueue> jobQueueInstance;
...
public JobQueue addQueue(String name)
{
JobQueue q = jobQueueInstance.get();
....
return q;
}
}
2) Possibly several JobQueue
s 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.
Turns out the solution was not so hard. Every day we learn. Yey!
SchedulerService
into the JobQueue
. This creates a circular depencency but oh well.SchedulerService
, make a proxy method deleteJob(JobQueue jq, Job j)
. All this does is invoke jq.deleteJob(j)
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.