blob: a076fc21a282325e2e1fc1082e6a46a2c02c912d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 2018 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* SAP AG - initial API and implementation
******************************************************************************/
package org.eclipse.ocl.examples.eventmanager.framework;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.LinkedList;
import java.util.WeakHashMap;
import java.util.logging.Logger;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EContentAdapter;
import org.eclipse.ocl.examples.eventmanager.EventFilter;
import org.eclipse.ocl.examples.eventmanager.EventManager;
import org.eclipse.ocl.examples.eventmanager.EventManagerFactory;
import org.eclipse.ocl.examples.eventmanager.filters.AbstractEventFilter;
/**
* A scalable implementation of the {@link EventManager} interface, using hash tables to quickly dispatch
* incoming {@link Notification}s to the sets of registered {@link Adapter event listeners}. Uses a single
* {@link EContentAdapter} to listen to all changes in those {@link ResourceSet}s for which it is responsible.
*
* @author Daniel Vocke (D044825)
* @author Axel Uhl (D043530)
*/
public class EventManagerTableBased implements EventManager {
private Logger logger = Logger.getLogger(EventManagerTableBased.class.getName());
private boolean active = true;
/**
* the EventAdapter instance for the EventManager
*/
private final EventAdapter adapter = new EventAdapter(this);
/**
* Toogle the {@link EventManager} off, on given <code>false</code> and no {@link Notification}s will be delivered
* @param doFireEventsValue <code>false</code> to disable {@link Notification}delivery, <code>true</code> to enable
*/
public void setFireEvents(boolean doFireEventsValue) {
doFireEvents = doFireEventsValue;
}
private boolean doFireEvents = true;
/**
* listeners are not notified directly. The notification process is done by the appropriate AdapterCapsule. This Map provides
* the associated AdapterCapsule for a Listener. For each type of Listener there is a separate AdapterCapsule (That's why
* there might be multiple AdapterCapsules for one Listener instance (the instance could have been registered multiple times))
*/
protected WeakHashMap<Adapter, Collection<AdapterCapsule>> notifierByListener = new WeakHashMap<Adapter, Collection<AdapterCapsule>>();
/**
* this is needed for performance reasons mainly
*/
private Collection<AdapterCapsule> allNotifiers = new LinkedList<AdapterCapsule>();
/**
* The RegistrationManager does the main work when finding out which listeners are affected by an event.
*/
private final RegistrationManagerTableBased registrationManager;
private final WeakHashMap<ResourceSet, Object> resourceSets;
/**
* Registered with all {@link WeakReference}s created for {@link Adapter}s
* during {@link #register(Adapter, AbstractEventFilter, ListenerTypeEnum)
* registration}. If any of these adapters is no longer strongly referenced
* and hence eligible for garbage collection, it may not have been properly
* {@link #deregister(Adapter) deregistered} from this event manager. This
* would cause structures in the {@link #registrationManager} to remain in
* place although no longer needed. This, in turn, would leak memory over
* time.
*/
private final ReferenceQueue<Adapter> adaptersNoLongerStronglyReferenced = new ReferenceQueue<Adapter>();
/**
* This thread polls the {@link #adaptersNoLongerStronglyReferenced}. For any {@link Adapter} that
* is enqueued, it {@link #deregister(Reference) deregisters} the adapter. The thread will only
* keep a weak reference to this event manager, hence not disabling the event manager's garbage
* collection.
*/
private CleanupThread adapterCleanupThread;
public EventManagerTableBased(ResourceSet set) {
this();
addToObservedResourceSets(set);
}
public EventManagerTableBased() {
resourceSets = new WeakHashMap<ResourceSet, Object>();
registrationManager = new RegistrationManagerTableBased();
adapterCleanupThread = new CleanupThread(adaptersNoLongerStronglyReferenced, this);
adapterCleanupThread.start();
}
public void setActive(boolean active) {
this.active = active;
}
/* Methods from EventRegistry interface */
/*
* @see EventRegistry#registerListener(ChangeListener, MoinEventFilter)
*/
public void subscribe(
EventFilter eventFilterTree, Adapter listener) {
register(listener, (AbstractEventFilter) eventFilterTree, ListenerTypeEnum.postChange);
}
/*
* @see EventRegistry#registerPreChangeListener(PreChangeListener, MoinEventFilter)
*/
public void registerPreChangeListener(Adapter listener, AbstractEventFilter eventFilterTree) {
register(listener, eventFilterTree, ListenerTypeEnum.preChange);
}
public void registerCommitListener(Adapter listener, AbstractEventFilter eventFilterTree) {
register(listener, eventFilterTree, ListenerTypeEnum.postCommit);
}
public void registerPreCommitListener(Adapter listener, AbstractEventFilter eventFilterTree) {
register(listener, eventFilterTree, ListenerTypeEnum.preCommit);
}
/*
* the following 2 constants define the types of listeners that get a Notifier or DeferringNotifier
*/
private static final ListenerTypeEnum listenersForNotifier = new ListenerTypeEnum(ListenerTypeEnum.postChange,
ListenerTypeEnum.preChange);
private static final ListenerTypeEnum listenersForDeferringNotifier = new ListenerTypeEnum(ListenerTypeEnum.postCommit,
ListenerTypeEnum.preCommit);
private void register(Adapter listener, AbstractEventFilter eventFilterTree, ListenerTypeEnum listenerType) {
// Check preconditions for parameters
if (listener == null) {
throw new IllegalArgumentException("Event listener must not be null");
}
if (eventFilterTree == null) {
throw new IllegalArgumentException("Event filter must not be null");
}
// Use WeakReference to avoid dangling registrations
WeakReference<Adapter> listenerRef = new WeakReference<Adapter>(listener, adaptersNoLongerStronglyReferenced);
// delegate registration to RegistrationManager
// The event filter is cloned, because the calculation of the DNF will modify the filter tree
registrationManager.register(eventFilterTree.clone(), listenerRef, listenerType);
// instantiate and associate notifier
AdapterCapsule notifier = null;
if (listenerType.matches(listenersForNotifier)) {
notifier = new AdapterCapsule(listenerRef, listenerType, this);
} else if (listenerType.matches(listenersForDeferringNotifier)) {
notifier = new DeferringNotifier(listenerRef, listenerType, this);
} else {
logger.warning("Unkown listenerType "+listenerType);
}
addNotifierForListener(notifier);
}
public void deregister(Adapter listener) {
// TODO what if a listener is being removed that has pending events?? -> EventDeferring
registrationManager.deregister(listener);
// remove Notifier(s) for listener
removeListener(listener);
}
void deregister(Reference<? extends Adapter> listenerRef) {
Adapter adapter = listenerRef.get();
if (adapter == null) {
// WeakHashMaps with adapter as key don't need to be taken care of anymore
registrationManager.deregister(listenerRef);
} else {
deregister(adapter);
}
}
/* Methods from EventManager interface */
// private static final ListenerTypeEnum listenerTypesToReceiveChangeEvents = new
// ListenerTypeEnum(ListenerTypeEnum.postChange,ListenerTypeEnum.preCommit,ListenerTypeEnum.postCommit);
public void fireChangeEvent(Notification event) {
if (!doFireEvents)
return;
// ((ChangeEventImpl) event).setDedicatedListenerType(listenerTypesToReceiveChangeEvents);
fireEvent(event);
// After the "PostEvent" has been fired, the cached information for the pre/post cycle can be deleted
// If this is not done, the event could not be used for another Session
// TODO currently not supported
// ((ChangeEventImpl) event).registrations = null;
}
public void firePreChangeEvent(Notification event) {
if (!doFireEvents)
return;
// ((ChangeEventImpl) event).setDedicatedListenerType(ListenerTypeEnum.preChange);
fireEvent(event);
}
public void beginCommand() {
if (!doFireEvents)
return;
for (AdapterCapsule notifier : allNotifiers)
notifier.deferNotification();
}
public void postCommitCommand() {
if (!doFireEvents)
return;
for (AdapterCapsule notifier : allNotifiers)
if (notifier.getListenerType().matches(ListenerTypeEnum.postCommit))
notifier.deliverDeferredEvents();
}
public void preCommitCommand() {
if (!doFireEvents)
return;
for (AdapterCapsule notifier : allNotifiers)
if (notifier.getListenerType().matches(ListenerTypeEnum.preCommit))
notifier.deliverDeferredEvents();
}
private static final ListenerTypeEnum allCommitListenerTypes = new ListenerTypeEnum(ListenerTypeEnum.preCommit,
ListenerTypeEnum.postCommit);
public void cancelCommand() {
if (!doFireEvents)
return;
for (AdapterCapsule notifier : allNotifiers)
if (notifier.getListenerType().matches(allCommitListenerTypes))
notifier.cancelDeferment();
}
/**
* This method notifies all interested listeners by invoking the fireEvent() method on their associated Notifier.
*
* @param event
* the event that will be delivered to clients
*/
private void fireEvent(Notification event) {
Collection<WeakReference<? extends Adapter>> listeners = registrationManager.getListenersFor(event);
for (WeakReference<? extends Adapter> listenerRef : listeners) {
AdapterCapsule notifier = getNotifierForListener(listenerRef, ListenerTypeEnum.postChange);
if (notifier != null) {
notifier.fireEvent(event);
}
}
}
/*
* ************************************************************************* The following 3 methods
* (addNotifierForListener,removeListener,getNotifierForListener) are private convenience methods only! They are needed
* because a listener that implements both ( PreChangeListener and ChangeListener) has to have a seperate Notifier for each
* role. (several registrations as e.g. PreChangeListener result in only one Notifier) In order to achieve this, a collection
* of Notifiers is stored for each listener and the notifiers can then be rejected from the Collection if they don't match the
* required ListenerType. *************************************************************************
*/
private void addNotifierForListener(AdapterCapsule notifier) {
Adapter adapter = notifier.getListener().get();
if (adapter == null) {
logger.warning("listener "+notifier.getListener()+" got GCed; AdapterCapsule: "+notifier);
} else {
Collection<AdapterCapsule> notifiers = notifierByListener.get(adapter);
if (notifiers == null) {
notifiers = new LinkedList<AdapterCapsule>();
notifierByListener.put(adapter, notifiers);
}
notifiers.add(notifier);
allNotifiers.add(notifier);
}
}
private void removeListener(Adapter listener) {
// maintain allNotifiers-member
Collection<AdapterCapsule> removedNotifiers = notifierByListener.get(listener);
if (removedNotifiers != null) {
allNotifiers.removeAll(removedNotifiers);
}
// maintain Map
notifierByListener.remove(listener);
}
private AdapterCapsule getNotifierForListener(WeakReference<? extends Adapter> listener, ListenerTypeEnum listenerType) {
Adapter adapter = listener.get();
if (adapter == null) {
logger.warning("No notifier found for listener "+listener);
} else {
Collection<AdapterCapsule> notifiers = notifierByListener.get(adapter);
if (notifiers == null) {
logger.warning("No notifiers found");
return null;
}
for (AdapterCapsule notifier : notifiers) {
if (notifier.isResponsibleFor(adapter, listenerType)) {
return notifier;
}
}
}
logger.warning("No notifier found");
return null;
}
public void handleEMFEvent(Notification notification) {
if (active) {
if (!notifierByListener.isEmpty()) {
for (Notification n : EventManagerFactory.eINSTANCE.createNotificationForComposites(notification)) {
fireChangeEvent(n);
}
}
}
}
public boolean unsubscribe(Adapter caller) {
deregister(caller);
return true;
}
@Override
protected void finalize() throws Throwable {
adapterCleanupThread.stopCleaner();
for (ResourceSet rs : resourceSets.keySet()) {
if (rs != null && adapter != null) {
rs.eAdapters().remove(adapter);
}
}
super.finalize();
}
/*
* EventDeferment will not be implemented yet:
*/
/*
* This method switches session scoped event deferment. This will result in the deferment of all events if the {@link
* deferEvents} flag is set true. No listeners that are currently connected to the SessionEventManager will receive events.
* The queued events are delivered when the operation is called and the {@link deferEvents} flag is set to false. If the
* session scoped deferment overrides a client scoped deferment, the previous setting will be restored afterwards. @param
* deferEvents - a flag indicating whether event deferring is switched on or off
*/
/*
* public void setEventDeferring(boolean deferEvents) { for (Iterator it = notifierByListener.values().iterator();
* it.hasNext();) { Object notifier = it.next(); if (notifier instanceof DeferrableNotifier) { ((DeferrableNotifier) notifier)
* .setGlobalEventDeferring(deferEvents); } } }
*/
/*
* This method switches client specific event deferment. If the {@link deferEvents} flag is set to true, no events will be
* delivered to the specified listener. The {@link notificationTrigger} set is used to specify event types that trigger the
* delivery of all currently queued events. The deferment will stay active in that case. When the operation is called and the
* {@link deferEvents} flag is set to false, the deferment will be turned off and all pending events will be delivered to the
* listener. @param listener - the affected listener @param deferEvents - a flag indicating whether the event deferment is
* switched on or off @param notificationTrigger - a set of event types that trigger the automatic delivery of queued events.
* The type of the contained elements is {@link java.lang.Class}
*/
/*
* public void setEventDeferring(ChangeListener listener, boolean deferEvents, Set notificationTrigger) { /* TODO move
* exception-message to xlf-file (but probably this method will be removed anyway) //only (post)ChangeListeners can be
* deferred... DeferrableNotifier notifier = (DeferrableNotifier) getNotifierForListener( listener,
* ChangeEvent.NOTIFICATIONTIME_AFTER_CHANGE); if (notifier == null) throw new IllegalStateException( "Cannot switch
* EventDeferring on listener that is not registered yet."); notifier.setEventDeferment(deferEvents);
* notifier.setNotificationTrigger(notificationTrigger); }
*/
public String toString() {
return registrationManager.toString();
}
public void addToObservedResourceSets(ResourceSet resourceSet) {
if (!resourceSet.eAdapters().contains(adapter)) {
resourceSet.eAdapters().add(adapter);
}
resourceSets.put(resourceSet, null);
}
public void removeFromObservedResourceSets(ResourceSet resourceSet) {
resourceSet.eAdapters().remove(adapter);
resourceSets.remove(resourceSet);
}
}