Vaadin-MVP-Lite icon indicating copy to clipboard operation
Vaadin-MVP-Lite copied to clipboard

My changes and a question

Open EduFrazao opened this issue 11 years ago • 2 comments

Hi @sockeqwe .

Im using a modified version of your EventBus here. Thanks, it is helping me a LOT. When my changes become stable, I will offer it to the project, and if you think that it haves any clue to your component proposal, i will be happy to contribute :).

Basically, my changes are:

  • A division little division:
    • Handler Manager (manages the envent handler registration and dispatching, but does not permit fire events over it). Its only to listen and respond for events.
      • An Application Handler Manager, that can be attached to the application context, allowing users to create application scoped handlers that will will receive application events.
      • An Session Handler Manager, that can be attached to any Vaadin Session, so the user can attach session scoped handlers that will have same lifecicle as the Vaadin Session. It will receive application and session scope events.
  • EventRouter
    • Allows users to fire events over it, bind other EventRouters on it, but not allow handler registrations. This is useful to memory management, avoiding a handler registration on a global object.
      • Session Router: A EventRouter that can have one instance per Vaadin Session, and is bound to application router, to receive global events (Like your GlobalEventBus). Events fired here, is delivered to all connected EventBus.
      • Application EventRouter (A Event Router proposed to be used as a singleton on a application. Events fired here is routed to all event routers, and in all their binded EventBus'es.
  • EventBus - Is a EventRouter and a HandlerManager, to be used in a component scope, like in a Presenter. It can be bound to the SessionRouter, to receive Session and Application Scope events. It can be used to fire events locally, or in session or application.

Well, its only a hierarchical separation with few implementations, but it is helping me, a lot. Now even more with Push Events, that can traverse session and application context.

About my Question. How you deal with Generic Events? Like:

EntitySavedEvent<Serializable>

eventBus.fireEvent(new EntitySavedEntity<Person>(person)); eventBus.fireEvent(new EntitySavedEntity<Book>(book));

Firing this two events will deliver the event to any EntitySaveEntity Handler, and if it is not expecting a Person or a Book, it will cause ClassCast Exceptions!

Thanks a lot!

EduFrazao avatar Sep 13 '13 20:09 EduFrazao

Hi @EduFrazao , Im happy to hear that someone uses my code :-D Any contribution is welcome!

Regarding your architectural changes: Basically it's a good idea to divide the EventBus in parts. But the most important thing is that the user/programmer does not have to care about how to configure the EventBus and how to stick the EventBus components together. So in my opinion there must be a fully working EventBus "out-of-the box" that can be instantiated with new EventBus(). If a user / programmer needs to configure an EventBus for special edge cases, than it would be a good idea to let programmer inject or define his own EventRouters or EventHandlers (or use your predefined). The most important thing on this topic is that you should work with Interfaces. Define EventRouter and EventHandler as Interface. So you can implement an EventBus that works with any EventRouter and EventHandler implementation of the programmers choice.

Btw. you can also instantiate multiple EventBuses right now and bind them to a session if you want to. And you can use one EventBus for certain types of Event where use another EventBus for other event types.

Regarding your question about generic Events: The problem here is that the current EventBus implementation uses reflections to invoke the handlers target method at runntime. Since generics are something only visible to the programmer at pre compile time there is no simple work around to solve this problem at runntime in the EventBus codes. The problem is, that at compile time the java compile will simple remove the generic class definitions and replace them with Java Objects class.

Example:

class EntitySavedEntity <T>{

    private T data;

    public EntitySavedEntity(T data){
        this.date = data; 
    }

}

will be at runtime something like this (after compiling your code):

class EntitySavedEntity{

    private Object data;

    public EntitySavedEntity(Object data){
        this.date = data; 
    }

}

So there is no easy way to distinguish EntitySavedEntity<Person> from EntitySavedEntity<Book> because at runtime there are both EntitySavedEntity<Object>.

So the simplest way is to define different Events (without Generics) like new PersonSavedEvent() new BookSavedEvent() or to work with raw types in the @EventHandler method and than you have to use instanceof to determine if you want to continue. But then you can not register a Handler for Person only or for Book only.

The best solution would be to change the complete EventBus code to work with annotation Processing. The annotation processor will be invoked at compile time to generate real java code from @EventHandler Annotation and than you could solve the generics problem because you use native java code at runtime to dispatch events instead of Reflections. Unfortunately I'm a little bit busier that I want to be, and therefore I have no time to implement something like that now.

sockeqwe avatar Sep 14 '13 15:09 sockeqwe

Hi @sockeqwe . Thanks for answer. My idea from the beginig is hide this structure from the final user.

So, I changed the EventBus to an Interface, and I inject a ready implementation on their presenters..

This is basically the structure:


package br.com.autmix.web.gui.mvp.event;

import java.io.Serializable;

/**
 * Um roteador de eventos concentra registros
 * de outros roteadores para replicar a propagacao
 * de eventos.
 * @author Eduardo Frazao
 *
 * @param <RT> - Hierarquia do eventrouter suportado para agregacao
 */
public interface EventRouter<RT extends EventRouter<?>> extends Serializable {

    /**
     * Adiciona um roteador a pilha para ouvir eventos disparados nesta origem
     * @param bus
     */
    public void bind(RT router);

    /**
     * Remove o router associado
     * @param bus
     */
    public void unbind(RT router);


    /**
     * Dispara um evento a pilha
     * @param event
     * @return
     */
    public boolean fireEvent(Event event);

    /**
     * Solicita ao roteador de Eventos, que libere todos os seus recursos
     * pois ele e mais necessario necessario
     */
    public void release();

}


package br.com.autmix.web.gui.mvp.event;

import java.io.Serializable;

/**
 * Gerenciador de Handlers
 * Este objeto permite o registro de handlers, para acionamento posterior, de acordo com suas assinaturas.
 * @author Eduardo Frazao
 *
 */
public interface HandlerManager extends Serializable {

    /**
     * Adiciona um EventHandler para despacho de eventos do Bus
     * Note que o Handler precista ter ao menos um metodo anotado com {@link EventHandler}
     * @param handler
     */
    public void addHandler(Object handler);

    /**
     * Adiciona um handler anonimo ao bus
     * @param handler
     */
    public <T extends Event> void addHandler(Handler<T> handler);

    /**
     * Remove o EventHandler
     * @param handler
     */
    public void removeHandler(Object handler);

    /**
     * Remove o Handler
     * @param handler
     */
    public <T extends Event> void removeHandler(Handler<T> handler);

}

package br.com.autmix.web.gui.mvp.event;

/**
 * Roteador de Eventos da Aplicacao.
 * Propaga todos os eventos disparados nele para 
 * para os roteadores de secao
 * @author Eduardo Frazao
 *
 */
public interface ApplicationRouter extends EventRouter<SessionRouter> {

}

package br.com.autmix.web.gui.mvp.event;

/**
 * Roteador da secao. Permite o registro
 * de Bus de Contexto
 * @author eduardo
 *
 */
public interface SessionRouter extends EventRouter<EventBus> {

    /**
     * Dispara um evento para ser replicado para a aplicacao
     * @param event
     * @param avoid
     * @return
     */
    public boolean fireApplicationEvent(Event event);

}

package br.com.autmix.web.gui.mvp.event;

/**
 * 
 * Barramento de eventos da API MVC do Autmix
 * Permite o roteamento de eventos, registro e acionamento dos handlers
 * @author Hannes Dorfmann (see above)
 * @home https://vaadin.com/directory/-/directory/addon/mvp-lite
 */
public interface EventBus extends EventRouter<EventRouter<?>>, HandlerManager {

    /**
     * Dispara um evento para o escopo de secao
     * @param event
     * @return
     */
    public boolean fireEventOnSession(Event event);

    /**
     * Dispara um evento para o escopo da aplicacao
     * @param event
     * @return
     */
    public boolean fireEventOnApplication(Event event);

    /**
     * Configura este eventbus para capturar eventos disparados
     * pelo roteador da secao.
     * Detalhe: Estes eventos podem ter sido roteados pelo 
     * roteador da aplicacao
     */
    public void bindOnSession();

    /**
     * Remove o registro do eventbus da Secao. Note que sempre que o eventbus for bindado na secao
     * e altamente recomendado remover sua bindagem quando nao for mais necessaria, evitando
     * problemas de gerenciamento de memoria devido ao relacionamento dos objetos
     */
    public void unbindFromSession();

}


And, im using Spring, so, the configuration is:

<!-- Implementacao de bus padrao -->
    <bean id="eventBus" class="br.com.autmix.web.gui.mvp.event.EventBusImpl" scope="prototype">
        <property name="useCache" value="true" />
        <property name="applicationRouter" ref="applicationRouter" />
        <property name="sessionRouter" ref="threadLocalSessionRouter" />
    </bean>
    <bean id="threadLocalSessionRouter" class="br.com.autmix.web.gui.session.Session" factory-method="sessionEventRouter" scope="prototype" />

    <!-- Implementacao de Bus da Aplicacao -->
    <bean id="sessionRouter" class="br.com.autmix.web.gui.main.context.eventbus.SessionRouterImpl" scope="prototype">
        <property name="applicationRouter" ref="applicationRouter" />
    </bean>

    <bean id="appHandlerManager" class="br.com.autmix.web.gui.main.context.eventbus.ApplicationHandlerManagerImpl" scope="singleton" />
    <bean id="applicationRouter" class="br.com.autmix.web.gui.main.context.eventbus.ApplicationRouterImpl" scope="singleton">
        <constructor-arg ref="applicationExecutor" />
        <constructor-arg ref="appHandlerManager" />
    </bean>
    <osgi:service ref="applicationRouter" interface="br.com.autmix.web.gui.mvp.event.ApplicationRouter" />
    <osgi:service ref="appHandlerManager" interface="br.com.autmix.web.gui.mvp.event.ApplicationHandlerManager" />

An detail: My ApplicationEventRouter uses an Thread Pool to fire events across all possible SessionRouters binded.

.. About the generic problems. I will make some tests here. If I found a suitable solution, for sure I will contribute.

Note: Sorry for the package name change of your work. Im working on a OSGi Application, and is more ease to make changes if the code is directly inside my bundles.

EduFrazao avatar Sep 14 '13 18:09 EduFrazao