/* * Jajuk * Copyright (C) The Jajuk Team * http://jajuk.info * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ package org.jajuk.events; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.jajuk.services.core.ExitService; import org.jajuk.util.log.Log; /** * This is a mediator managing relationships between subjects and observers * <p> * All notification methods are synchronized to assure event order. */ public final class ObservationManager { /** one event -> list of components. */ static ObserverRegistry observerRegistry = new ObserverRegistry(); /** Last event for a given subject (used for new objects that just registrated to this subject). */ static Map<JajukEvents, Properties> hLastEventBySubject = new HashMap<JajukEvents, Properties>(10); /** The queue itself. Must be synchronized, so we use a ConcurrentLinkedQueue which is thread-safe */ static BlockingQueue<JajukEvent> queue = new LinkedBlockingQueue<JajukEvent>(); /** The observation fifo. */ private static ObservationManagerThread observationThread; /** * Empty constructor to avoid instantiating this utility class. */ private ObservationManager() { } /** * Register a component for a list of subjects. This calls the * interface @see Observer.getRegistrationKeys() to retrieve * a list of events which the Observer is interested in. * * @param observer The Observer to register. */ public static synchronized void register(Observer observer) { Set<JajukEvents> eventSubjectSet = observer.getRegistrationKeys(); for (JajukEvents subject : eventSubjectSet) { Log.debug("Register: \"" + subject + "\" by: " + observer); observerRegistry.register(subject, observer); } } /** * Unregister a component for a list of subjects. * * @param observer The Observer to unregister. * * @see Observer.getRegistrationKeys() is called on the Observer * to get the list of events. */ public static synchronized void unregister(Observer observer) { Set<JajukEvents> eventSubjectSet = observer.getRegistrationKeys(); // can return null if no keys are registered if (eventSubjectSet == null) { return; } for (JajukEvents subject : eventSubjectSet) { boolean bRemoved = observerRegistry.unregister(subject, observer); if (bRemoved) { Log.debug("Unregister: \"" + subject + "\" from: " + observer); } } } /** * Notify all components having registered for the given subject. * * @param event The event that is triggered including any additional * data that is of interest as part of the event. */ public static void notify(JajukEvent event) { // asynchronous notification by default to avoid // exception throw in the register current thread try { /* * We don't launch it in a regular thread because EDT waits thread end to * display */ queue.add(event); // synchronize here to avoid creating more than one observation manager // thread synchronized (ObservationManager.class) { if (observationThread == null || !observationThread.isAlive()) { // If the thread is terminated, a new thread must be instantiated // Otherwise an IllegalThreadStateException is thrown Log.debug("Observation Manager thread not running, start a new one"); observationThread = new ObservationManagerThread(); observationThread.start(); } } } catch (Error e) { // Make sure to catch any error (Memory or IllegalThreadStateException for // ie, this notification musn't stop the current work) Log.error(e); } } /** * Notify synchronously all components having registered for the given subject. * * @param event The event that is triggered including any additional * data that is of interest as part of the event. */ public static void notifySync(JajukEvent event) { JajukEvents subject = event.getSubject(); Log.debug("Notify: " + subject); // save last event hLastEventBySubject.put(subject, event.getDetails()); observerRegistry.notifySync(event); } /** * Return whether the event already occurred at least once. * * @param subject The type of event to check for. * * @return true, if contains event, false otherwise. */ public static boolean containsEvent(JajukEvents subject) { return hLastEventBySubject.containsKey(subject); } /** * Return the details for last event of the given subject, or null if there is * no details. * * @param subject The type of event to check for. * @param sDetailName The detailed piece of information to fetch. * * @return the detail as an object or null if the event or the detail doesn't * exist */ public static Object getDetailLastOccurence(JajukEvents subject, String sDetailName) { Properties pDetails = hLastEventBySubject.get(subject); if (pDetails != null) { return pDetails.get(sDetailName); } return null; } /** * Return the details for an event, or null if there is no details. * * @param event The event to retrieve the detail from. * @param sDetailName The detailed piece of information to fetch. * * @return the detail as an object or null if the event or the detail doesn't * exist */ public static Object getDetail(JajukEvent event, String sDetailName) { Properties pDetails = event.getDetails(); if (pDetails != null) { return pDetails.get(sDetailName); } return null; } /** * Return the details for an event, or null if there is no details. * * @param subject The event to query for. * * @return the details or null there are not details */ public static Properties getDetailsLastOccurence(JajukEvents subject) { return hLastEventBySubject.get(subject); } /** * Remove all registered Observers. This is mainly used in Unit Tests * to get a clean state again. */ public static void clear() { hLastEventBySubject.clear(); queue.clear(); observerRegistry.clear(); } } /** * Observation manager thread that consumes events asynchronously */ class ObservationManagerThread extends Thread { ObservationManagerThread() { super("Observation Manager Thread"); } @Override public void run() { // Stop to execute events is thread flag is set or if Jajuk is exiting while (!ExitService.isExiting()) { try { final JajukEvent event = ObservationManager.queue.poll(1000, TimeUnit.MILLISECONDS); if (event != null) { // launch action asynchronously new Thread("Event Executor for: " + event.toString()) { @Override public void run() { ObservationManager.notifySync(event); } }.start(); } // Make sure to handle any exception or error to avoid the observation // system to die. Throwable covers all types of Exceptions/Errors. } catch (Throwable e) { Log.error(e); } } } }