we have a java EE app that has a core module and some other modules. In the core module exists a scheduler, declared as follows:
@Singleton
@Transactional
@RunAs("admintask")
@RunAsPrincipal("CreateDataScheduler")
public class CreateDataScheduler
But in a module that we run, we want to replace this scheduler with another, so that the CreateDataScheduler is not ran at all, only the CreateDataScheduler. How I can accomplish this? I tried with @Specializes annotation, but both schedulers are called.
@Singleton
@Transactional
@RunAs("admintask")
@RunAsPrincipal("CreateDataScheduler")
@Specializes //annotated
public class CreateDataSchedulerTEST extends CreateDataScheduler
I also put this as alternatives in beans.xml file, but get an error: Enabled alternative class CreateDataSchedulerTEST does not match any bean, or is not annotated with @Alternative or an @Alternative stereotype, or does not declare a producer annotated with @Alternative or an @Alternative stereotype"
What is the right way to do this?
I am going to assume the modules are in different deployments (EARs/WARs).
As we discussed in the comments, the problem here is that the method annotated with @Schedule
is handled by the EJB container. So no point trying to override it through CDI - even though the EJBs containing the method are also CDI beans.
There is however an elegant solution to this, assuming both schedulings are the same (e.g. every 30 minutes) and and the 2nd module is in a different deployment that the core module (2 different WAR/EAR files):
Keep only 1 @Singleton
EJB, the one in the core module. Let it depend on a CDI component and the scheduler method simply delegates to the CDI component, which contains all the actual logic. In your other module, that is deployed only in the second deployment, specialize the CDI component just as you describe in the question. Now the scheduler of the second deployment will pick up the specializer component.
Sample code:
// Core module
@Singleton
@Transactional
@RunAs("admintask")
@RunAsPrincipal("CreateDataScheduler")
public class CreateDataScheduler {
@Inject SchedulingLogic schedulingLogic;
@Schedule(hour = "*", minute = "*/30", persistent = false)
public void start() {
schedulingLogic.start();
}
}
/**
* This is the CDI component that contains the scheduling logic
* in the core module.
*/
@ApplicationScoped
public class SchedulingLogic {
public void start() {
// do stuff...
}
}
In the second module:
/**
* This is the CDI component that contains the specialized
* scheduling logic of the second module.
*/
@ApplicationScoped
@Specializes
public class SchedulingLogicTEST extends SchedulingLogic {
public void start() {
// do stuff specific to the second module...
}
}
If the schedulings are not the same, you may need some more effort but the principle is the same. Just use programmatic scheduling, supported by the EJB specs, instead of declarative.
IMO CDI is very powerful, much more than EJBs. It is also a more generic solution, e.g. applicable to standalone "SE" applications, not just "EE" ones. I believe it is a good idea to write your application and business logic in an environment-neutral way and CDI helps with that. Components like CreateDataScheduler
are adaptors from the specific environment (in this case: the EJB container that knows how to schedule jobs) to your environment-neutral business logic. If this logic needs to be run standalone, e.g. from a SE application, it is trivial to add e.g. a Quartz scheduler, or even the built-in ScheduledExecutorService
/ScheduledThreadPoolExecutor
that calls the same logic. (Another thing that helps your logic be environment-neutral is constructor injection - this way components can be instantiated directly with new
and you don't even need CDI, Spring etc in the SE case; but that's another story)