= Agendamento de Eventos CDI
:index-group: CDI
:jbake-type: page
:jbake-status: published

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.

[source,java]
----
@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;
        }
    }
}
----

Então para usar isto, temos de injetar `Scheduler` como um EJB e aproveitar.

[source,java]
----
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:

[source,java]
----
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import jakarta.ejb.AccessTimeout;
import jakarta.ejb.EJB;
import jakarta.ejb.ScheduleExpression;
import jakarta.ejb.embeddable.EJBContainer;
import jakarta.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étodo `timeout` 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étodo `timeout`. 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.