/******************************************************************************* * Copyright (c) 2009, 2012 SAP AG and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.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); } }