Open florianschmitt opened 8 years ago
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);
}
}
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.
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