package au.gov.ga.earthsci.notification; import java.util.LinkedHashSet; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.e4.core.contexts.IEclipseContext; import org.eclipse.e4.core.di.annotations.Creatable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import au.gov.ga.earthsci.common.util.ExtensionRegistryUtil; import au.gov.ga.earthsci.common.util.ExtensionRegistryUtil.Callback; /** * A threadsafe manager that gives access to the user notification mechanism. * <p/> * The manager maintains a list of {@link INotificationReceiver}s which can * register to receive user notifications. The manager invokes each of these * handlers when a new user notification is received. * <p/> * Plugins can provide additional {@link INotificationReceiver} implementations * using the {@value #NOTIFICATION_RECEIVER_EXTENSION_POINT_ID} extension point. * These can be discovered at runtime and registered with the manager via the * {@link #loadReceivers(IExtensionRegistry, IEclipseContext)} method. * <p/> * Additionally, notification categories can be registered using the * {@value #NOTIFICATION_CATEGORY_PROVIDER_EXTENSION_POINT_ID} extension point. * These can be discovered at runtime and registered with the manager via the * {@link #registerNotificationCategories(IExtensionRegistry, IEclipseContext)} * method. * * @author James Navin (james.navin@ga.gov.au) */ @Creatable @Singleton public class NotificationManager { public static final String NOTIFICATION_RECEIVER_EXTENSION_POINT_ID = "au.gov.ga.earthsci.notification.receivers"; //$NON-NLS-1$ public static final String NOTIFICATION_CATEGORY_PROVIDER_EXTENSION_POINT_ID = "au.gov.ga.earthsci.notification.categoryProviders"; //$NON-NLS-1$ public static final String NOTIFICATION_RECEIVER_CLASS_ATTRIBUTE = "class"; //$NON-NLS-1$ private static Set<INotificationReceiver> receivers = new LinkedHashSet<INotificationReceiver>(); private static ReadWriteLock receiversLock = new ReentrantReadWriteLock(); private static ExecutorService notifier = Executors.newSingleThreadExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r, "NotificationManager notifier thread"); //$NON-NLS-1$ } }); private static final Logger logger = LoggerFactory.getLogger(NotificationManager.class); @Inject public void postConstuct(IEclipseContext context) { loadReceiversFromExtensions(context); registerNotificationCategories(context); } /** * Load registered notification receivers from the provided extension * registry. * <p/> * This method will inject dependencies on retrieved receivers using the * provided eclipse context, as appropriate. * * @param registry * The extension registry to search for notification receivers * @param context * The context to use for dependency injection etc. */ public static void loadReceiversFromExtensions(IEclipseContext context) { logger.info("Registering notification receivers"); //$NON-NLS-1$ try { ExtensionRegistryUtil.createFromExtension(NOTIFICATION_RECEIVER_EXTENSION_POINT_ID, NOTIFICATION_RECEIVER_CLASS_ATTRIBUTE, INotificationReceiver.class, context, new Callback<INotificationReceiver>() { @Override public void run(INotificationReceiver receiver, IConfigurationElement element, IEclipseContext context) { context.set(element.getAttribute(NOTIFICATION_RECEIVER_CLASS_ATTRIBUTE), receiver); registerReceiver(receiver); } }); } catch (CoreException e) { logger.error("Exception while loading receivers", e); //$NON-NLS-1$ } } /** * Registered additional {@link NotificationCategory}s from * {@link INotificationCategoryProvider}s registered against the * {@value #NOTIFICATION_CATEGORY_PROVIDER_EXTENSION_POINT_ID} extension * point. * <p/> * This method will inject dependencies on retrieved receivers using the * provided eclipse context, as appropriate. */ public static void registerNotificationCategories(IEclipseContext context) { logger.info("Registering notification categories"); //$NON-NLS-1$ try { ExtensionRegistryUtil.createFromExtension(NOTIFICATION_CATEGORY_PROVIDER_EXTENSION_POINT_ID, NOTIFICATION_RECEIVER_CLASS_ATTRIBUTE, INotificationCategoryProvider.class, context, new Callback<INotificationCategoryProvider>() { @Override public void run(INotificationCategoryProvider provider, IConfigurationElement element, IEclipseContext context) { provider.registerNotificationCategories(); } }); } catch (CoreException e) { logger.error("Exception while loading categories", e); //$NON-NLS-1$ } } /** * Notify the user with the provided notification object * * @param notification */ public static void notify(final INotification notification) { if (notification == null) { return; } notifier.execute(new Runnable() { @Override public void run() { receiversLock.readLock().lock(); try { for (INotificationReceiver receiver : receivers) { receiver.handle(notification); } } finally { receiversLock.readLock().unlock(); } } }); } /** * Register the provided notification receiver so that it is able to receive * user notifications as they are generated. * * @param receiver * The receiver to register */ public static void registerReceiver(INotificationReceiver receiver) { if (receiver == null) { return; } logger.debug("Registered notification receiver: {}", receiver.getClass()); //$NON-NLS-1$ receiversLock.writeLock().lock(); try { receivers.add(receiver); } finally { receiversLock.writeLock().unlock(); } } /** * Remove the provided notification receiver so that it no longer receives * user notifications as they are generated. * * @param receiver * The receiver to register */ public static void removeReceiver(INotificationReceiver receiver) { if (receiver == null) { return; } logger.debug("Removed notification receiver {0}", receiver.getClass()); //$NON-NLS-1$ receiversLock.writeLock().lock(); try { receivers.remove(receiver); } finally { receiversLock.writeLock().unlock(); } } /** * Remove all registered receivers from this manager */ public static void removeAllRecievers() { receiversLock.writeLock().lock(); try { receivers.clear(); } finally { receiversLock.writeLock().unlock(); } } /** * Create an error level notification with no acknowledgement, and pass it * to the {@link #notify(INotification)} method. * * @param title * Title of the notification * @param text * Text of the notification * @param category * Notification category */ public static void error(String title, String text, NotificationCategory category) { notify(Notification.create(NotificationLevel.ERROR, title, text).inCategory(category).build()); } /** * Create an error level notification with no acknowledgement, and pass it * to the {@link #notify(INotification)} method. * * @param title * Title of the notification * @param text * Text of the notification * @param category * Notification category * @param throwable * Notification throwable */ public static void error(String title, String text, NotificationCategory category, Throwable throwable) { notify(Notification.create(NotificationLevel.ERROR, title, text).inCategory(category).withThrowable(throwable) .build()); } /** * Create an warning level notification with no acknowledgement, and pass it * to the {@link #notify(INotification)} method. * * @param title * Title of the notification * @param text * Text of the notification * @param category * Notification category */ public static void warning(String title, String text, NotificationCategory category) { notify(Notification.create(NotificationLevel.WARNING, title, text).inCategory(category).build()); } /** * Create an information level notification with no acknowledgement, and * pass it to the {@link #notify(INotification)} method. * * @param title * Title of the notification * @param text * Text of the notification * @param category * Notification category */ public static void info(String title, String text, NotificationCategory category) { notify(Notification.create(NotificationLevel.INFORMATION, title, text).inCategory(category).build()); } }