Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Eventbus - allow custom scopes in eventbus addon #289

Open
florianschmitt opened this issue Jun 15, 2016 · 2 comments
Open

Eventbus - allow custom scopes in eventbus addon #289

florianschmitt opened this issue Jun 15, 2016 · 2 comments

Comments

@florianschmitt
Copy link

Hi, thanks for the great add on.
I'm using the eventbus addon and stumbled upon an issue. I had the need for modal windows in my application, since I didn't want a view-change in that case, I created my own custom scope SingleModalWindowScope. I'm still testing, but it seems to do the job.
My problem is now, that I reuse views, in the modal window, which use the ViewEventBus. Now if a event is fired, possibly both, the view in the background and the modal window are listing to it.
I checked the eventbus code and I guess I could implement my own ScopedEventBus, but I wouldn't be able to use it with the current API because of the EventScope enum, which only allows the predefined scopes. Do you see any problems with e.g. giving the EventScope enum a field for the scope-name and allowing
<T> void publish(EventScope scope, String topic, Object sender, T payload) throws UnsupportedOperationException;

to overload for instance a newly introduced method

<T> void publish(String scopeName, String topic, Object sender, T payload) throws UnsupportedOperationException;

I didn't analyze the whole code, do you see any other problems with allowing custom scopes in the eventbus?

Thanks!

BR Florian

@florianschmitt
Copy link
Author

Hi again,

I needed a solution, so I put together a hack. I implemented a ViewEventBus, which is pausable. With this, I can set all registered listeners to paused when I open a modal window and set them to play again, when the window is closed... seems a bit dirty, but it does the job for now. Here is my implementation if anyone has a similar problem:

PausableViewEventBus.java

package org.vaadin.spring.events.internal;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ReflectionUtils;
import org.vaadin.spring.events.EventBus.ViewEventBus;
import org.vaadin.spring.events.EventScope;
import org.vaadin.spring.events.annotation.EventBusListenerMethod;
import org.vaadin.spring.events.internal.ListenerCollection.Listener;
import org.vaadin.spring.util.ClassUtils;

/**
 * This class allows pausing subscribed listeners (used for allowing modal
 * windows which use the same events as an opened view).
 * 
 * WARNING: currently only listeners, subscribed with @EventBusListenerMethod
 * are paused, NO EventBusListener implementations.
 * 
 * @author fschmitt
 *
 */
public class PausableViewEventBus extends ScopedEventBus implements ViewEventBus {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    public PausableViewEventBus(UIEventBus parentEventBus) {
        super(EventScope.VIEW, parentEventBus);
    }

    /**
     * Switches MethodListenerWrapper to PausableMethodListenerWrapper
     * implementation to allow pausing.
     */
    @Override
    public void subscribe(Object listener, boolean includingPropagatingEvents) {
        logger.trace("Subscribing listener [{}] to event bus [{}], includingPropagatingEvents = {}", listener, this,
                includingPropagatingEvents);

        final int[] foundMethods = new int[1];
        ClassUtils.visitClassHierarchy(new ClassUtils.ClassVisitor() {
            @Override
            public void visit(Class<?> clazz) {
                for (Method m : clazz.getDeclaredMethods()) {
                    if (m.isAnnotationPresent(EventBusListenerMethod.class)) {
                        if (m.getParameterTypes().length == 1) {
                            logger.trace("Found listener method [{}] in listener [{}]", m.getName(), listener);
                            MethodListenerWrapper l = new PausableMethodListenerWrapper(PausableViewEventBus.this,
                                    listener, includingPropagatingEvents, m);
                            accessListenersField().add(l);
                            foundMethods[0]++;
                        } else {
                            throw new IllegalArgumentException(
                                    "Listener method " + m.getName() + " does not have the required signature");
                        }
                    }
                }
            }
        }, listener.getClass());

        if (foundMethods[0] == 0) {
            logger.warn("Listener [{}] did not contain a single listener method!", listener);
        }
    }

    /**
     * Pauses or unpauses all currently subscribed MethodListeners.
     * 
     * @param value
     *            pause or unpause
     */
    public void setAllListenersPaused(boolean value) {
        Set<Listener> listenersFromListenerCollection = accessListenersFieldFromListenerCollection();
        listenersFromListenerCollection.stream()//
                .filter(l -> PausableMethodListenerWrapper.class.isAssignableFrom(l.getClass()))//
                .map(PausableMethodListenerWrapper.class::cast)//
                .forEach(l -> l.setPaused(value));
    }

    private Set<Listener> accessListenersFieldFromListenerCollection() {
        Field listenersField = ReflectionUtils.findField(ListenerCollection.class, "listeners", Set.class);
        listenersField.setAccessible(true);
        try {
            @SuppressWarnings({ "unchecked", "rawtypes" })
            Set<Listener> result = (Set) listenersField.get(accessListenersField());
            return result;
        } catch (IllegalArgumentException | IllegalAccessException e) {
            ReflectionUtils.handleReflectionException(e);
            return null;
        }

    }

    private ListenerCollection accessListenersField() {
        Field listenersField = ReflectionUtils.findField(getClass(), "listeners", ListenerCollection.class);
        listenersField.setAccessible(true);
        try {
            ListenerCollection result = (ListenerCollection) listenersField.get(this);
            return result;
        } catch (IllegalArgumentException | IllegalAccessException e) {
            ReflectionUtils.handleReflectionException(e);
            return null;
        }
    }
}

PausableMethodListenerWrapper.java

package org.vaadin.spring.events.internal;

import java.lang.reflect.Method;

import org.vaadin.spring.events.Event;
import org.vaadin.spring.events.EventBus;

import lombok.Setter;

public class PausableMethodListenerWrapper extends MethodListenerWrapper {

    public PausableMethodListenerWrapper(EventBus owningEventBus, Object listenerTarget,
            boolean includingPropagatingEvents, Method listenerMethod) {
        super(owningEventBus, listenerTarget, includingPropagatingEvents, listenerMethod);
    }

    @Setter
    private boolean paused;

    @Override
    public boolean supports(Event<?> event) {
        if (paused)
            return false;
        return super.supports(event);
    }
}

PausableMethodListenerWrapper.java

package com.explicatis.system.scope;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.vaadin.spring.events.EventBus;
import org.vaadin.spring.events.internal.PausableViewEventBus;

import com.vaadin.spring.internal.ViewScopeImpl;

@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class EventBusConfigurationForPausableViewEventBus {

    @Autowired
    @Lazy
    private EventBus.UIEventBus uiEventBus;

    @Bean
    @Scope(value = ViewScopeImpl.VAADIN_VIEW_SCOPE_NAME, proxyMode = ScopedProxyMode.NO)
    @Primary
    EventBus.ViewEventBus viewEventBus() {
        return new PausableViewEventBus(uiEventBus);
    }
}

@peholmst
Copy link
Owner

Thanks for your input. I'll have a look at what could be done to support other scopes in the future. Unfortunately I've been busy with other projects so it might take a while.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants