@Singleton
@Lock(LockType.READ)
public class Scheduler {
@Resource
private TimerService timerService;
@Resource
private BeanManager beanManager;
public void scheduleEvent(ScheduleExpression schedule, Object event, Annotation... qualifiers) {
timerService.createCalendarTimer(schedule, new TimerConfig(new EventConfig(event, qualifiers), false));
}
@Timeout
private void timeout(Timer timer) {
final EventConfig config = (EventConfig) timer.getInfo();
beanManager.fireEvent(config.getEvent(), config.getQualifiers());
}
//Na verdade não precisa ser serializável, só tem que implementá-lo
private final class EventConfig implements Serializable {
private final Object event;
private final Annotation[] qualifiers;
private EventConfig(Object event, Annotation[] qualifiers) {
this.event = event;
this.qualifiers = qualifiers;
}
public Object getEvent() {
return event;
}
public Annotation[] getQualifiers() {
return qualifiers;
}
}
}
Agendamento de Eventos CDI
Este exemplo utiliza uma boa combinação CDI/EJB para agendar Eventos CDI. Isto é util se você quiser Eventos CDI para disparar regularmente ou em um horário específico ou data do calendário.
Efetivamente este é um simples empacotador em volta do método BeanManager.fireEvent(Object,Annotations...)
que adiciona ScheduleExpression
na mistura.
@Timeout e ScheduleExpression
A lógica aqui é simples, nós efetivamente expômos um método identico para BeanManager.fireEvent(Object, Annotations...)
e empacotamos isso como scheduleEvent(ScheduleExpression, Object, Annotation...)
Para fazer isso nós usamos o TimerService
EJB (embaixo dessa cobertura isso é Quartz) e criamos um método @Timeout
que vai ser executado quando ativada a ScheduleExpression
.
O método @Timeout
, simplesmente chamado timeout
, pega o evento e dispara ele.
Então para usar isto, temos de injetar Scheduler
como um EJB e aproveitar.
public class SomeBean {
@EJB
private Scheduler scheduler;
public void doit() throws Exception {
// a cada cinco minutos
final ScheduleExpression schedule = new ScheduleExpression()
.hour("*")
.minute("*")
.second("*/5");
scheduler.scheduleEvent(schedule, new TestEvent("five"));
}
/**
* Evento será disparado a cada cinco minutos
*/
public void observe(@Observes TestEvent event) {
// process the event
}
}
Caso de Teste
Um caso de teste de trabalho para o acima seria como segue:
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import javax.ejb.AccessTimeout;
import javax.ejb.EJB;
import javax.ejb.ScheduleExpression;
import javax.ejb.embeddable.EJBContainer;
import javax.enterprise.event.Observes;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* @version $Revision$ $Date$
*/
public class SchedulerTest {
public static final CountDownLatch events = new CountDownLatch(3);
@EJB
private Scheduler scheduler;
@Test
public void test() throws Exception {
final ScheduleExpression schedule = new ScheduleExpression()
.hour("*")
.minute("*")
.second("*/5");
scheduler.scheduleEvent(schedule, new TestEvent("five"));
Assert.assertTrue(events.await(1, TimeUnit.MINUTES));
}
@AccessTimeout(value = 1, unit = TimeUnit.MINUTES)
public void observe(@Observes TestEvent event) {
if ("five".equals(event.getMessage())) {
events.countDown();
}
}
public static class TestEvent {
private final String message;
public TestEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
@Before
public void setup() throws Exception {
EJBContainer.createEJBContainer().getContext().bind("inject", this);
}
}
Você deve conhecer
-
Eventos CDI não são mutli-tarefas
Se houver 0 observadores e cada um deles levar 7 minutos para executar, então o tempo total de execução para um evento são 70 minutos. Ele iria fazer você absolutamente, não gostar de agendar este evento, para disparar frequentemente com mais que 70 minutos.
O que aconteceria se você fizesse ? Depende da política @Singleton
@Lock
-
@Lock(WRITE)
é o padrão. Neste modo o métodotimeout
seria essencialemnte bloqueado até que a invocação anterior se complete. Tendo ele disparado a cada 5 minutos, mesmo que você só possa processar um a cada 70 minutos, eventualmente faria com que todas as tarefas agrupadas de temporizador estivessem esperando em seu Singleton. -
@Lock(READ)
pemitir a execução paralela do métodotimeout
. Eventos serão disparados em paralelo por um tempo. Contudo, desde que eles estejam levando 70 minutos cada, dentro de uma hora mais ou menos, ficaremos sem tarefas agrupadas de temporizador exatamente como acima.
A solução elegante é usar @Lock(WRITE)
então especifique algum tempo limite curto como @AccessTimeout(value = 1, unit = TimeUnit.MINUTES)
no método timeout
. Quando a próxima invocação de 5 minutos for disparada, ela aguardará até 1 minuto para ter acesso ao Singleton antes de desistir.
== APIs Used