All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.elusive_code.newsboy.AsyncEventService Maven / Gradle / Ivy

Go to download

Asynchronous publish-subscribe communication library. Features: 1. Listeners are stored using WeakReferences to prevent memory leaks when they are not unsubscribed. 2. Uses fork-join framework for concurrent event delivery. 3. Publish methods returns collection of Futures that represent event notifications. 4. Supports ordered publishing: guaranteed to notify of the events in order they were published. 5. Provides EventSource and EventService interfaces for better integration with IOC containers and alternative implementations.

The newest version!
/*
 * Copyright 2014. Vladislav Dolgikh
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.elusive_code.newsboy;

import java.util.*;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveAction;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * 

Implementation of {@link com.elusive_code.newsboy.EventService} that uses

*
    *
  • weak references to store subscribers
  • *
  • asynchronous, and uses Fork-Join framework to schedule notifications
  • *
* * @see com.elusive_code.newsboy.EventService * @see com.elusive_code.newsboy.Subscribe * @author Vladislav Dolgikh */ public class AsyncEventService implements EventService { private static final Logger LOG = Logger.getLogger(AsyncEventService.class.getName()); /** *

Subscribed listeners. Used for faster unsubscribing

*

Key - listener object, Value - set of event handlers

*/ private WeakHashMap> listeners = new WeakHashMap<>(); /** *

Listeners by event class, used for faster publishing

*

Key - class of event to handle, Value - set of event handlers from all listeners

*/ private Map> listenersByClass = new HashMap<>(); /** * Lock for synchronizing listeners' collections */ private Lock listenersLock = new ReentrantLock(); private PublishAction lastOrderedEvent = null; private ForkJoinPool notificatorPool; private boolean saveEventStackTrace; public AsyncEventService() { this(Runtime.getRuntime().availableProcessors()); } public AsyncEventService(int parallelism) { this(parallelism, true); } public AsyncEventService(int parallelism, boolean saveEventStackTrace) { this.notificatorPool = new ForkJoinPool(parallelism); this.saveEventStackTrace = saveEventStackTrace; } /** *

Whether event publishing stack trace is stored

* * @see #setSaveEventStackTrace(boolean) * @return true if it is stored */ public boolean isSaveEventStackTrace() { return saveEventStackTrace; } /** *

* Defines whether to store event publishing stack trace. *

*

* If true, then if error occur during event handling it will attempt to add that stack trace * to the one of occurred exception.
* Event if it fails, that stack trace will be available through {@link EventNotifierTask#getEventStackTrace()} *

*

* By default it is true *

* @param saveEventStackTrace flag whether to store event stack trace */ public void setSaveEventStackTrace(boolean saveEventStackTrace) { this.saveEventStackTrace = saveEventStackTrace; } @Override public void subscribe(Object object) { if (object == null) return; LinkedList eventHandlers = EventServiceHelper.createObjectEventHandlers(object); listenersLock.lock(); try { listeners.put(object, eventHandlers); for (WeakEventHandler handler : eventHandlers) { addListenerByClass(handler.getEventType(), handler); } } finally { listenersLock.unlock(); } } @Override public void unsubscribe(Object object) { if (object == null) return; listenersLock.lock(); try { Collection handlers = listeners.remove(object); if (handlers == null || handlers.size() <= 0) return; for (WeakEventHandler handler : handlers) { removeListenerByClass(handler.getEventType(), handler); } } finally { listenersLock.unlock(); } } /** *

Publish event to this EventService.

*

No delivery order guaranteed.

*

When using returned futures keep in mind they may fail with * {@link com.elusive_code.newsboy.WeakReferenceCollectedException} * in that case nothing should be done. * Listener was claimed by GC before event handling (but after event scheduling)

* @param event event to notify of * @return list of {@link com.elusive_code.newsboy.NotificationFuture} that represent scheduled notifications */ @Override public List publish ( Object event ) { if ( event == null ) return Collections.EMPTY_LIST; EventStackTrace stackTrace = null; if (saveEventStackTrace){ stackTrace = new EventStackTrace(event); } PublishAction task = new PublishAction ( event, stackTrace ); notificatorPool.execute ( task ); return new ArrayList(task.getNotifiers()); } /** *

Publish event to this EventService.

*

Guaranteed to deliver in the same order that was published * relative to other ordered events

*

When using returned futures keep in mind they may fail with * {@link com.elusive_code.newsboy.WeakReferenceCollectedException} * in that case nothing should be done. * Listener was claimed by GC before event handling (but after event scheduling)

* @param event event to notify of * @return list of {@link com.elusive_code.newsboy.NotificationFuture} that represent scheduled notifications */ @Override @Subscribe public List publishOrdered ( Object event ) { if ( event == null ) return Collections.EMPTY_LIST; EventStackTrace stackTrace = null; if (saveEventStackTrace){ stackTrace = new EventStackTrace(event); } lastOrderedEvent = new PublishAction ( event, lastOrderedEvent, true, stackTrace ); notificatorPool.execute ( lastOrderedEvent ); return new ArrayList(lastOrderedEvent.getNotifiers()); } private void addListenerByClass (Class clazz, WeakEventHandler handler) { Set handlers = listenersByClass.get(clazz); if (handlers == null) { handlers = new HashSet(); listenersByClass.put(clazz,handlers); } handlers.add(handler); } private void removeListenerByClass(Class clazz, WeakEventHandler handler) { Set handlers = listenersByClass.get(clazz); if (handlers == null) return; handlers.remove(handler); } /** * Task that initiates event notifications and handles ordering */ protected class PublishAction extends RecursiveAction { private Object event; private List notifiers; private PublishAction previousEvent; private boolean ordered; private EventStackTrace stackTrace; public PublishAction(Object event, EventStackTrace stackTrace) { this(event, null, false, stackTrace); } public PublishAction(Object event, PublishAction previousEvent, boolean ordered, EventStackTrace stackTrace) { this.event = event; this.ordered = ordered; this.previousEvent = previousEvent; this.stackTrace = stackTrace; this.notifiers = Collections.unmodifiableList(collectNotifiers()); } public List getNotifiers() { return notifiers; } /** * Collects all notifiers for current event * @return list of notification tasks */ private LinkedList collectNotifiers() { LinkedList notifiers = new LinkedList<>(); listenersLock.lock(); try { Set classes = EventServiceHelper.collectClassHierarchy(event.getClass()); for (Class clazz : classes) { Set handlers = listenersByClass.get(clazz); if (handlers != null) { Iterator i = handlers.iterator(); while (i.hasNext()) { WeakEventHandler eventHandler = i.next(); Object listener = eventHandler.getTarget(); if (listener == null) { //listener collected by GC i.remove(); } else { EventNotifierTask task = new EventNotifierTask(eventHandler, event, AsyncEventService.this, stackTrace); notifiers.add(task); } } } } return notifiers; } finally { listenersLock.unlock(); } } protected void compute() { try { //if event ordered and it's not first one wait for it's turn if (previousEvent != null) { previousEvent.quietlyJoin(); } //scheduling notification for (EventNotifierTask task : getNotifiers()) { task.fork(); } //if event ordered we should wait for notifications to complete //so that next event won't fire until we notify of this one if (ordered) { for (EventNotifierTask task : getNotifiers()) { task.quietlyJoin(); } } } finally { //for processed events we need to set previous to null to prevent memory leak //(chaining events with hard references like current event->prev->prev->.....->first event) previousEvent = null; } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy