package org.intellimate.izou.events; import org.intellimate.izou.util.AddonThreadPoolUser; import org.intellimate.izou.util.IzouModule; import org.intellimate.izou.identification.Identification; import org.intellimate.izou.identification.IllegalIDException; import org.intellimate.izou.main.Main; import org.intellimate.izou.resource.ResourceModel; import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; /** * This class gets all the Events from all registered EventPublisher, generates Resources and passes them to the * OutputManager. Can also be used to fire Events Concurrently. */ public class EventDistributor extends IzouModule implements Runnable, AddonThreadPoolUser { private BlockingQueue<EventModel<?>> events = new LinkedBlockingQueue<>(); private ConcurrentHashMap<Identification, EventPublisher> registered = new ConcurrentHashMap<>(); //here are all the Instances to to control the Event-dispatching stored private final ConcurrentLinkedQueue<EventsControllerModel> eventsControllers = new ConcurrentLinkedQueue<>(); //here are all the Listeners stored private final ConcurrentHashMap<String, ArrayList<EventListenerModel>> listeners = new ConcurrentHashMap<>(); //here are all the Listeners stored that get called when an Event finishes processing private final ConcurrentHashMap<String, ArrayList<EventListenerModel>> finishListeners = new ConcurrentHashMap<>(); private boolean stop = false; public EventDistributor(Main main) { super(main); main.getThreadPoolManager().getIzouThreadPool().submit(this); } /** * fires the event concurrently, this is generally discouraged. * <p> * This method should not be used for normal Events, for for events which obey the following laws:<br> * 1. they are time critical.<br> * 2. addons are not expected to react in any way beside a small update<br> * 3. they are few.<br> * if your event matches the above laws, you may consider firing it concurrently. * </p> * @param eventModel the EventModel */ public void fireEventConcurrently(EventModel<?> eventModel) { if(eventModel == null) return; submit(() -> processEvent(eventModel)); } /** * with this method you can register EventPublisher add a Source of Events to the System. * <p> * This method represents a higher level of abstraction! Use the EventManager to fire Events! * This method is intended for use cases where you have an entire new source of events (e.g. network) * @param identification the Identification of the Source * @return An Optional Object which may or may not contains an EventPublisher * @throws IllegalIDException not yet implemented */ public Optional<EventCallable> registerEventPublisher(Identification identification) throws IllegalIDException { if(registered.containsKey(identification)) return Optional.empty(); EventPublisher eventPublisher = new EventPublisher(events); registered.put(identification, eventPublisher); return Optional.of(eventPublisher); } /** * with this method you can unregister EventPublisher add a Source of Events to the System. * <p> * This method represents a higher level of abstraction! Use the EventManager to fire Events! * This method is intended for use cases where you have an entire new source of events (e.g. network) * @param identification the Identification of the Source */ public void unregisterEventPublisher(Identification identification) { if(!registered.containsKey(identification)) return; registered.remove(identification); } /** * Registers an EventController to control EventDispatching-Behaviour * <p> * Method is thread-safe. * It is expected that this method executes quickly. * * @param controller the EventController Interface to control event-dispatching * @throws IllegalIDException not yet implemented */ public void registerEventsController(EventsControllerModel controller) throws IllegalIDException { eventsControllers.add(controller); } /** * Unregisters an EventController * <p> * Method is thread-safe. * * @param controller the EventController Interface to remove */ public void unregisterEventsController(EventsControllerModel controller) { eventsControllers.remove(controller); } /** * Adds an listener for events. * <p> * Be careful with this method, it will register the listener for ALL the informations found in the Event. If your * event-type is a common event type, it will fire EACH time!. * It will also register for all Descriptors individually! * It will also ignore if this listener is already listening to an Event. * Method is thread-safe. * </p> * @param event the Event to listen to (it will listen to all descriptors individually!) * @param eventListener the ActivatorEventListener-interface for receiving activator events * @throws IllegalIDException not yet implemented */ @SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter"}) public void registerEventListener(EventModel event, EventListenerModel eventListener) throws IllegalIDException { registerEventListener(event.getAllInformations(), eventListener); } /** * Adds an listener for events. * <p> * It will register for all ids individually! * This method will ignore if this listener is already listening to an Event. * Method is thread-safe. * </p> * @param ids this can be type, or descriptors etc. * @param eventListener the ActivatorEventListener-interface for receiving activator events */ @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") public void registerEventListener(List<String> ids, EventListenerModel eventListener) { for(String id : ids) { ArrayList<EventListenerModel> listenersList = listeners.get(id); if (listenersList == null) { listeners.put(id, new ArrayList<>()); listenersList = listeners.get(id); } if (!listenersList.contains(eventListener)) { synchronized (listenersList) { listenersList.add(eventListener); } } } } /** * unregister an EventListener * * It will unregister for all Descriptors individually! * It will also ignore if this listener is not listening to an Event. * Method is thread-safe. * * @param event the Event to stop listen to * @param eventListener the ActivatorEventListener used to listen for events * @throws IllegalArgumentException if Listener is already listening to the Event or the id is not allowed */ @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") public void unregisterEventListener(EventModel<EventModel> event, EventListenerModel eventListener) throws IllegalArgumentException { for (String id : event.getAllInformations()) { ArrayList<EventListenerModel> listenersList = listeners.get(id); if (listenersList == null) { return; } synchronized (listenersList) { listenersList.remove(eventListener); } } } /** * unregister an EventListener * * It will unregister for all registered descriptors. * It will also ignore if this listener is not listening to an Event. * Method is thread-safe. * * @param eventListener the ActivatorEventListener used to listen for events * @throws IllegalArgumentException if Listener is already listening to the Event or the id is not allowed */ @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") public void unregisterEventListener(EventListenerModel eventListener) throws IllegalArgumentException { listeners.values().stream() .forEach(list -> { synchronized (list) { list.removeIf(eventListenerModel -> eventListenerModel.equals(eventListener)); } }); } /** * Adds an listener for events that gets called when the event finished processing. * <p> * Be careful with this method, it will register the listener for ALL the informations found in the Event. If your * event-type is a common event type, it will fire EACH time!. * It will also register for all Descriptors individually! * It will also ignore if this listener is already listening to an Event. * Method is thread-safe. * </p> * @param event the Event to listen to (it will listen to all descriptors individually!) * @param eventListener the ActivatorEventListener-interface for receiving activator events * @throws IllegalIDException not yet implemented */ @SuppressWarnings({"SynchronizationOnLocalVariableOrMethodParameter"}) public void registerEventFinishedListener(EventModel event, EventListenerModel eventListener) throws IllegalIDException { registerEventFinishedListener(event.getAllInformations(), eventListener); } /** * Adds an listener for events that gets called when the event finished processing. * <p> * It will register for all ids individually! * This method will ignore if this listener is already listening to an Event. * Method is thread-safe. * </p> * @param ids this can be type, or descriptors etc. * @param eventListener the ActivatorEventListener-interface for receiving activator events */ @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") public void registerEventFinishedListener(List<String> ids, EventListenerModel eventListener) { for(String id : ids) { ArrayList<EventListenerModel> listenersList = finishListeners.get(id); if (listenersList == null) { finishListeners.put(id, new ArrayList<>()); listenersList = finishListeners.get(id); } if (!listenersList.contains(eventListener)) { synchronized (listenersList) { listenersList.add(eventListener); } } } } /** * unregister an EventListener that got called when the event finished processing. * * It will unregister for all Descriptors individually! * It will also ignore if this listener is not listening to an Event. * Method is thread-safe. * * @param event the Event to stop listen to * @param eventListener the ActivatorEventListener used to listen for events * @throws IllegalArgumentException if Listener is already listening to the Event or the id is not allowed */ @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") public void unregisterEventFinishedListener(EventModel<EventModel> event, EventListenerModel eventListener) throws IllegalArgumentException { for (String id : event.getAllInformations()) { ArrayList<EventListenerModel> listenersList = finishListeners.get(id); if (listenersList == null) { return; } synchronized (listenersList) { listenersList.remove(eventListener); } } } /** * unregister an EventListener that got called when the event finished processing. * * It will unregister for all registered descriptors. * It will also ignore if this listener is not listening to an Event. * Method is thread-safe. * * @param eventListener the ActivatorEventListener used to listen for events * @throws IllegalArgumentException if Listener is already listening to the Event or the id is not allowed */ @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") public void unregisterEventFinishedListener(EventListenerModel eventListener) throws IllegalArgumentException { finishListeners.values().stream() .forEach(list -> { synchronized (list) { list.removeIf(eventListenerModel -> eventListenerModel.equals(eventListener)); } }); } /** * Checks whether to dispatch an event * * @param event the fired Event * @return true if the event should be fired */ private boolean checkEventsControllers(EventModel event) { List<CompletableFuture<Boolean>> collect = eventsControllers.stream() .map(controller -> submit(() -> controller.controlEventDispatcher(event)).thenApply(result -> { if (!result) debug("Event: " + event + " is canceled by " + controller.getID()); return result; })) .collect(Collectors.toList()); try { collect = timeOut(collect, 1000); } catch (InterruptedException e) { debug("interrupted"); } return collect.stream() .map(future -> { try { return future.get(); } catch (InterruptedException | ExecutionException e) { return null; } }) .filter(Objects::nonNull) .noneMatch(bool -> !bool); } public BlockingQueue<EventModel<?>> getEvents() { return events; } /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see Thread#run() */ @Override public void run() { while(!stop) { try { EventModel<?> event = events.take(); processEvent(event); } catch (InterruptedException e) { log.warn("interrupted", e); } } } /** * process the Event * @param event the event to process */ private void processEvent(EventModel<?> event) { if (!event.getSource().isCreatedFromInstance()) { error("event: " + event + "has invalid source"); return; } debug("EventFired: " + event.toString() + " from " + event.getSource().getID()); submit(() -> event.lifecycleCallback(EventLifeCycle.START)); if (checkEventsControllers(event)) { submit(() -> event.lifecycleCallback(EventLifeCycle.APPROVED)); submit(() -> event.lifecycleCallback(EventLifeCycle.RESOURCE)); List<ResourceModel> resourceList = getMain().getResourceManager().generateResources(event); event.addResources(resourceList); submit(() -> event.lifecycleCallback(EventLifeCycle.LISTENERS)); List<EventListenerModel> listenersTemp = event.getAllInformations().parallelStream() .map(listeners::get) .filter(Objects::nonNull) .flatMap(Collection::stream) .distinct() .collect(Collectors.toList()); List<CompletableFuture> futures = listenersTemp.stream() .map(eventListener -> submit(() -> eventListener.eventFired(event))) .collect(Collectors.toList()); try { timeOut(futures, 1000); } catch (InterruptedException e) { error("interrupted", e); } submit(() -> event.lifecycleCallback(EventLifeCycle.OUTPUT)); getMain().getOutputManager().passDataToOutputPlugins(event); submit(() -> event.lifecycleCallback(EventLifeCycle.ENDED)); List<EventListenerModel> finishListenersTemp = event.getAllInformations().parallelStream() .map(finishListeners::get) .filter(Objects::nonNull) .flatMap(Collection::stream) .distinct() .collect(Collectors.toList()); futures = finishListenersTemp.stream() .map(eventListener -> submit(() -> eventListener.eventFired(event))) .collect(Collectors.toList()); try { timeOut(futures, 1000); } catch (InterruptedException e) { error("interrupted", e); } } else { debug("canceling: " + event.toString() + " from " + event.getSource().getID()); submit(() -> event.lifecycleCallback(EventLifeCycle.CANCELED)); } } /** * stops the EventDistributor */ public void stop() { stop = true; } /** * This class is used to pass Events to the EventDistributor */ private class EventPublisher implements EventCallable { //the queue where all the Events are stored private final BlockingQueue<EventModel<?>> events; protected EventPublisher(BlockingQueue<EventModel<?>> events) { this.events = events; } /** * use this method to fire Events. * @param event the Event to fire */ public void fire(EventModel event) { if(event == null) return; events.add(event); } } }