/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Distributable under LGPL license.
* See terms of license at gnu.org.
*/
package net.java.sip.communicator.service.notification;
import java.util.*;
import net.java.sip.communicator.service.configuration.*;
import net.java.sip.communicator.service.notification.event.*;
import net.java.sip.communicator.util.*;
import static net.java.sip.communicator.service.notification.NotificationAction.*;
import static net.java.sip.communicator.service.notification.event.NotificationActionTypeEvent.*;
import static net.java.sip.communicator.service.notification.event.NotificationEventTypeEvent.*;
/**
* The implementation of the NotificationService.
*
* @author Yana Stamcheva
* @author Ingo Bauersachs
*/
class NotificationServiceImpl
implements NotificationService
{
private final Logger logger
= Logger.getLogger(NotificationServiceImpl.class);
private final ConfigurationService configService =
NotificationServiceActivator.getConfigurationService();
private static final String NOTIFICATIONS_PREFIX
= "net.java.sip.communicator.impl.notifications";
/**
* A set of all registered event notifications.
*/
private final Map notifications
= new HashMap();
/**
* A set of all registered event notifications.
*/
private final Map defaultNotifications
= new HashMap();
/**
* Contains the notification handler per action type.
*/
private final Map handlers
= new HashMap();
/**
* A list of all registered NotificationChangeListeners.
*/
private final List changeListeners
= new Vector();
/**
* Queue to cache fired notifications before all handlers are registered.
*/
private Queue notificationCache
= new LinkedList();
/**
* Creates an instance of NotificationServiceImpl by loading all
* previously saved notifications.
*/
NotificationServiceImpl()
{
// Load all previously saved notifications.
this.loadNotifications();
}
/**
* Creates a new EventNotification or obtains the corresponding
* existing one and registers a new action in it.
*
* @param eventType the name of the event (as defined by the plugin that's
* registering it) that we are setting an action for.
* @param action the NotificationAction responsible for
* handling the given actionType
*/
public void registerNotificationForEvent( String eventType,
NotificationAction action)
{
Notification notification = null;
if(notifications.containsKey(eventType))
notification = notifications.get(eventType);
else
{
notification = new Notification(eventType);
notifications.put(eventType, notification);
this.fireNotificationEventTypeEvent(
EVENT_TYPE_ADDED, eventType);
}
Object existingAction = notification.addAction(action);
// We fire the appropriate event depending on whether this is an
// already existing actionType or a new one.
if (existingAction != null)
{
fireNotificationActionTypeEvent(
ACTION_CHANGED,
eventType,
action);
}
else
{
fireNotificationActionTypeEvent(
ACTION_ADDED,
eventType,
action);
}
// Save the notification through the ConfigurationService.
this.saveNotification( eventType,
action,
true,
false);
}
/**
* Creates a new EventNotification or obtains the corresponding
* existing one and registers a new action in it.
*
* @param eventType the name of the event (as defined by the plugin that's
* registering it) that we are setting an action for.
* @param actionType the type of the action that is to be executed when the
* specified event occurs (could be one of the ACTION_XXX fields).
* @param actionDescriptor a String containing a description of the action
* (a URI to the sound file for audio notifications or a command line for
* exec action types) that should be executed when the action occurs.
* @param defaultMessage the default message to use if no specific message
* has been provided when firing the notification.
*/
public void registerNotificationForEvent( String eventType,
String actionType,
String actionDescriptor,
String defaultMessage)
{
if (logger.isDebugEnabled())
logger.debug("Registering event " + eventType + "/" +
actionType + "/" + actionDescriptor + "/" + defaultMessage);
if (actionType.equals(ACTION_SOUND))
{
Notification notification = defaultNotifications.get(eventType);
SoundNotificationAction action =
(SoundNotificationAction) notification.getAction(ACTION_SOUND);
registerNotificationForEvent (
eventType,
new SoundNotificationAction(
actionDescriptor,
action.getLoopInterval()));
}
else if (actionType.equals(ACTION_LOG_MESSAGE))
{
registerNotificationForEvent (eventType,
new LogMessageNotificationAction(
LogMessageNotificationAction.INFO_LOG_TYPE));
}
else if (actionType.equals(ACTION_POPUP_MESSAGE))
{
registerNotificationForEvent (eventType,
new PopupMessageNotificationAction(defaultMessage));
}
else if (actionType.equals(ACTION_COMMAND))
{
registerNotificationForEvent (eventType,
new CommandNotificationAction(actionDescriptor));
}
}
/**
* Removes the EventNotification corresponding to the given
* eventType from the table of registered event notifications.
*
* @param eventType the name of the event (as defined by the plugin that's
* registering it) to be removed.
*/
public void removeEventNotification(String eventType)
{
notifications.remove(eventType);
this.fireNotificationEventTypeEvent(
EVENT_TYPE_REMOVED, eventType);
}
/**
* Removes the given actionType from the list of actions registered for the
* given eventType.
*
* @param eventType the name of the event (as defined by the plugin that's
* registering it) for which we'll remove the notification.
* @param actionType the type of the action that is to be executed when the
* specified event occurs (could be one of the ACTION_XXX fields).
*/
public void removeEventNotificationAction( String eventType,
String actionType)
{
Notification notification
= notifications.get(eventType);
if(notification == null)
return;
NotificationAction action = notification.getAction(actionType);
if(action == null)
return;
notification.removeAction(actionType);
saveNotification(
eventType,
action,
false,
false);
fireNotificationActionTypeEvent(
ACTION_REMOVED,
eventType,
action);
}
/**
* Returns an iterator over a list of all events registered in this
* notification service. Each line in the returned list consists of
* a String, representing the name of the event (as defined by the plugin
* that registered it).
*
* @return an iterator over a list of all events registered in this
* notifications service
*/
public Iterable getRegisteredEvents()
{
return Collections.unmodifiableSet(
notifications.keySet());
}
/**
* Returns the notification action corresponding to the given
* eventType and actionType.
*
* @param eventType the type of the event that we'd like to retrieve.
* @param actionType the type of the action that we'd like to retrieve a
* descriptor for.
* @return the notification action of the action to be executed
* when an event of the specified type has occurred.
*/
public NotificationAction getEventNotificationAction(
String eventType,
String actionType)
{
Notification notification = notifications.get(eventType);
if(notification == null)
return null;
return notification.getAction(actionType);
}
/**
* Adds the given listener to the list of change listeners.
*
* @param listener the listener that we'd like to register to listen for
* changes in the event notifications stored by this service.
*/
public void addNotificationChangeListener(
NotificationChangeListener listener)
{
synchronized (changeListeners)
{
changeListeners.add(listener);
}
}
/**
* Removes the given listener from the list of change listeners.
*
* @param listener the listener that we'd like to remove
*/
public void removeNotificationChangeListener(
NotificationChangeListener listener)
{
synchronized (changeListeners)
{
changeListeners.remove(listener);
}
}
/**
* Adds an object that executes the actual action of a notification action.
* If the same action type is added twice, the last added wins.
*
* @param handler The handler that executes the action.
*/
public void addActionHandler(NotificationHandler handler)
{
if(handler == null)
throw new IllegalArgumentException("handler cannot be null");
synchronized(handlers)
{
handlers.put(handler.getActionType(), handler);
if(handlers.size() == NUM_ACTIONS && notificationCache != null)
{
for(NotificationData event : notificationCache)
fireNotification(event);
notificationCache.clear();
notificationCache = null;
}
}
}
/**
* Removes an object that executes the actual action of notification action.
* @param actionType The handler type to remove.
*/
public void removeActionHandler(String actionType)
{
if(actionType == null)
throw new IllegalArgumentException("actionType cannot be null");
synchronized(handlers)
{
handlers.remove(actionType);
}
}
/**
* Gets a list of handler for the specified action type.
*
* @param actionType the type for which the list of handlers should be
* retrieved or null if all handlers shall be returned.
*/
public Iterable getActionHandlers(String actionType)
{
if (actionType != null)
return Collections.singleton(handlers.get(actionType));
else
return handlers.values();
}
/**
* Executes a notification data object on the handlers.
* @param data The notification data to act upon.
*/
private void fireNotification(NotificationData data)
{
Notification notification = notifications.get(data.getEventType());
if(notification == null || !notification.isActive())
return;
for(NotificationAction action : notification.getActions().values())
{
String actionType = action.getActionType();
if(!action.isEnabled() || !handlers.containsKey(actionType))
continue;
NotificationHandler handler = handlers.get(actionType);
if (actionType.equals(ACTION_POPUP_MESSAGE))
{
((PopupMessageNotificationHandler) handler)
.popupMessage((PopupMessageNotificationAction) action,
data.getTitle(), data.getMessage(),
data.getIcon(), data.getTag());
}
else if (actionType.equals(ACTION_LOG_MESSAGE))
{
((LogMessageNotificationHandler) handler)
.logMessage((LogMessageNotificationAction) action,
data.getMessage());
}
else if (actionType.equals(ACTION_SOUND))
{
((SoundNotificationHandler) handler)
.start((SoundNotificationAction) action, data);
}
else if (actionType.equals(ACTION_COMMAND))
{
((CommandNotificationHandler) handler)
.execute(
(CommandNotificationAction)action,
data.getExtra());
}
}
}
/**
* If there is a registered event notification of the given
* eventType and the event notification is currently activated, the
* list of registered actions is executed.
*
* @param eventType the type of the event that we'd like to fire a
* notification for.
* @param title the title of the given message
* @param message the message to use if and where appropriate (e.g. with
* systray or log notification.)
* @param icon the icon to show in the notification if and where appropriate
* @param tag additional info to be used by the notification handler
*
* @return An object referencing the notification. It may be used to stop a
* still running notification. Can be null if the eventType is
* unknown or the notification is not active.
*/
public NotificationData fireNotification(
String eventType,
String title,
String message,
byte[] icon,
Object tag)
{
return fireNotification(eventType,
title,
message,
null,
icon,
tag);
}
/**
* If there is a registered event notification of the given
* eventType and the event notification is currently activated, the
* list of registered actions is executed.
*
* @param eventType the type of the event that we'd like to fire a
* notification for.
* @param title the title of the given message
* @param message the message to use if and where appropriate (e.g. with
* systray or log notification.)
* @param extra the extra data to pass (especially for Command execution)
* @param icon the icon to show in the notification if and where appropriate
* @param tag additional info to be used by the notification handler
*
* @return An object referencing the notification. It may be used to stop a
* still running notification. Can be null if the eventType is
* unknown or the notification is not active.
*/
public NotificationData fireNotification(
String eventType,
String title,
String message,
Map extra,
byte[] icon,
Object tag)
{
Notification notification = notifications.get(eventType);
if(notification == null || !notification.isActive())
return null;
NotificationData data = new NotificationData(eventType, title,
message, extra, icon, tag);
//cache the notification when the handlers are not yet ready
if (notificationCache != null)
notificationCache.add(data);
else
fireNotification(data);
return data;
}
/**
* If there is a registered event notification of the given
* eventType and the event notification is currently activated, we
* go through the list of registered actions and execute them.
*
* @param eventType the type of the event that we'd like to fire a
* notification for.
*
* @return An object referencing the notification. It may be used to stop a
* still running notification. Can be null if the eventType is
* unknown or the notification is not active.
*/
public NotificationData fireNotification(String eventType)
{
return this.fireNotification(eventType, null, null, null, null, null);
}
/**
* Saves the event notification given by these parameters through the
* ConfigurationService.
*
* @param eventType the name of the event
* @param actionType the type of action
* @param action the notification action to change
*/
private void saveNotification( String eventType,
NotificationAction action,
boolean isActive,
boolean isDefault)
{
String eventTypeNodeName = null;
String actionTypeNodeName = null;
List eventTypes = configService
.getPropertyNamesByPrefix(NOTIFICATIONS_PREFIX, true);
for (String eventTypeRootPropName : eventTypes)
{
String eType = configService.getString(eventTypeRootPropName);
if(eType.equals(eventType))
eventTypeNodeName = eventTypeRootPropName;
}
// If we didn't find the given event type in the configuration we save
// it here.
if(eventTypeNodeName == null)
{
eventTypeNodeName = NOTIFICATIONS_PREFIX
+ ".eventType"
+ Long.toString(System.currentTimeMillis());
configService.setProperty(eventTypeNodeName, eventType);
}
// if we set active/inactive for the whole event notification
if(action == null)
{
configService.setProperty(
eventTypeNodeName + ".active",
Boolean.toString(isActive));
return;
}
// Go through contained actions.
String actionPrefix = eventTypeNodeName + ".actions";
List actionTypes = configService
.getPropertyNamesByPrefix(actionPrefix, true);
for (String actionTypeRootPropName : actionTypes)
{
String aType = configService.getString(actionTypeRootPropName);
if(aType.equals(action.getActionType()))
actionTypeNodeName = actionTypeRootPropName;
}
Map configProperties = new HashMap();
// If we didn't find the given actionType in the configuration we save
// it here.
if(actionTypeNodeName == null)
{
actionTypeNodeName = actionPrefix
+ ".actionType"
+ Long.toString(System.currentTimeMillis());
configProperties.put(actionTypeNodeName, action.getActionType());
}
if(action instanceof SoundNotificationAction)
{
SoundNotificationAction soundAction
= (SoundNotificationAction) action;
configProperties.put(
actionTypeNodeName + ".soundFileDescriptor",
soundAction.getDescriptor());
configProperties.put(
actionTypeNodeName + ".loopInterval",
soundAction.getLoopInterval());
}
else if(action instanceof PopupMessageNotificationAction)
{
PopupMessageNotificationAction messageAction
= (PopupMessageNotificationAction) action;
configProperties.put(
actionTypeNodeName + ".defaultMessage",
messageAction.getDefaultMessage());
}
else if(action instanceof LogMessageNotificationAction)
{
LogMessageNotificationAction logMessageAction
= (LogMessageNotificationAction) action;
configProperties.put(
actionTypeNodeName + ".logType",
logMessageAction.getLogType());
}
else if(action instanceof CommandNotificationAction)
{
CommandNotificationAction commandAction
= (CommandNotificationAction) action;
configProperties.put(
actionTypeNodeName + ".commandDescriptor",
commandAction.getDescriptor());
}
configProperties.put(
actionTypeNodeName + ".enabled",
Boolean.toString(isActive));
configProperties.put(
actionTypeNodeName + ".default",
Boolean.toString(isDefault));
configService.setProperties(configProperties);
}
/**
* Loads all previously saved event notifications.
*/
private void loadNotifications()
{
List eventTypes = configService
.getPropertyNamesByPrefix(NOTIFICATIONS_PREFIX, true);
for (String eventTypeRootPropName : eventTypes)
{
boolean isEventActive =
isEnabled(eventTypeRootPropName + ".active");
String eventType
= configService.getString(eventTypeRootPropName);
List actions = configService
.getPropertyNamesByPrefix(
eventTypeRootPropName + ".actions", true);
for (String actionPropName : actions)
{
String actionType = configService.getString(actionPropName);
NotificationAction action = null;
if(actionType.equals(ACTION_SOUND))
{
String soundFileDescriptor
= configService.getString(
actionPropName + ".soundFileDescriptor");
String loopInterval
= configService.getString(
actionPropName + ".loopInterval");
action = new SoundNotificationAction(
soundFileDescriptor,
Integer.parseInt(loopInterval));
}
else if(actionType.equals(ACTION_POPUP_MESSAGE))
{
String defaultMessage
= configService.getString(
actionPropName + ".defaultMessage");
action = new PopupMessageNotificationAction(defaultMessage);
}
else if(actionType.equals(ACTION_LOG_MESSAGE))
{
String logType
= configService.getString(
actionPropName + ".logType");
action = new LogMessageNotificationAction(logType);
}
else if(actionType.equals(ACTION_COMMAND))
{
String commandDescriptor
= configService.getString(
actionPropName + ".commandDescriptor");
action = new CommandNotificationAction(commandDescriptor);
}
action.setEnabled(isEnabled(actionPropName + ".enabled"));
// Load the data in the notifications table.
Notification notification = notifications.get(eventType);
if(notification == null)
{
notification = new Notification(eventType);
notifications.put(eventType, notification);
}
notification.setActive(isEventActive);
notification.addAction(action);
}
}
}
private boolean isEnabled(String configProperty)
{
Object isEnabledObj = configService.getProperty(configProperty);
// if setting is missing we accept it is true
// this way we not affect old saved settings
if(isEnabledObj == null)
return true;
else
return Boolean.parseBoolean((String)isEnabledObj);
}
/**
* Finds the EventNotification corresponding to the given
* eventType and marks it as activated/deactivated.
*
* @param eventType the name of the event, which actions should be activated
* /deactivated.
* @param isActive indicates whether to activate or deactivate the actions
* related to the specified eventType.
*/
public void setActive(String eventType, boolean isActive)
{
Notification eventNotification
= notifications.get(eventType);
if(eventNotification == null)
return;
eventNotification.setActive(isActive);
saveNotification(eventType, null, isActive, false);
}
/**
* Finds the EventNotification corresponding to the given
* eventType and returns its isActive status.
*
* @param eventType the name of the event (as defined by the plugin that's
* registered it) that we are checking.
* @return true
if actions for the specified eventType
* are activated, false
- otherwise. If the given
* eventType is not contained in the list of registered event
* types - returns false
.
*/
public boolean isActive(String eventType)
{
Notification eventNotification
= notifications.get(eventType);
if(eventNotification == null)
return false;
return eventNotification.isActive();
}
/**
* Notifies all registered NotificationChangeListeners that a
* NotificationEventTypeEvent has occurred.
*
* @param eventType the type of the event, which is one of EVENT_TYPE_XXX
* constants declared in the NotificationEventTypeEvent class.
* @param sourceEventType the eventType, for which this event is
* about
*/
private void fireNotificationEventTypeEvent(String eventType,
String sourceEventType)
{
if (logger.isDebugEnabled())
logger.debug("Dispatching NotificationEventType Change. Listeners="
+ changeListeners.size()
+ " evt=" + eventType);
NotificationEventTypeEvent event
= new NotificationEventTypeEvent(this, eventType, sourceEventType);
for (NotificationChangeListener listener : changeListeners)
{
if (eventType.equals(EVENT_TYPE_ADDED))
{
listener.eventTypeAdded(event);
}
else if (eventType.equals(EVENT_TYPE_REMOVED))
{
listener.eventTypeRemoved(event);
}
}
}
/**
* Notifies all registered NotificationChangeListeners that a
* NotificationActionTypeEvent has occurred.
*
* @param eventType the type of the event, which is one of ACTION_XXX
* constants declared in the NotificationActionTypeEvent class.
* @param sourceEventType the eventType, which is the parent of the
* action
* @param action the notification action
*/
private void fireNotificationActionTypeEvent(
String eventType,
String sourceEventType,
NotificationAction action)
{
NotificationActionTypeEvent event
= new NotificationActionTypeEvent( this,
eventType,
sourceEventType,
action);
for(NotificationChangeListener listener : changeListeners)
{
if (eventType.equals(ACTION_ADDED))
{
listener.actionAdded(event);
}
else if (eventType.equals(ACTION_REMOVED))
{
listener.actionRemoved(event);
}
else if (eventType.equals(ACTION_CHANGED))
{
listener.actionChanged(event);
}
}
}
private boolean isDefault(String eventType, String actionType)
{
List eventTypes = configService
.getPropertyNamesByPrefix(NOTIFICATIONS_PREFIX, true);
for (String eventTypeRootPropName : eventTypes)
{
String eType
= configService.getString(eventTypeRootPropName);
if(!eType.equals(eventType))
continue;
List actions = configService
.getPropertyNamesByPrefix(
eventTypeRootPropName + ".actions", true);
for (String actionPropName : actions)
{
String aType
= configService.getString(actionPropName);
if(!aType.equals(actionType))
continue;
Object isDefaultdObj =
configService.getProperty(actionPropName + ".default");
// if setting is missing we accept it is true
// this way we override old saved settings
if(isDefaultdObj == null)
return true;
else
return Boolean.parseBoolean((String)isDefaultdObj);
}
}
return true;
}
/**
* Creates a new default EventNotification or obtains the
* corresponding existing one and registers a new action in it.
*
* @param eventType the name of the event (as defined by the plugin that's
* registering it) that we are setting an action for.
* @param action the NotificationAction to register
*/
public void registerDefaultNotificationForEvent(
String eventType,
NotificationAction action)
{
if(isDefault(eventType, action.getActionType()))
{
NotificationAction h =
getEventNotificationAction(eventType,
action.getActionType());
boolean isNew = false;
if(h == null)
{
isNew = true;
h = action;
}
this.saveNotification( eventType,
action,
h.isEnabled(),
true);
Notification notification = null;
if(notifications.containsKey(eventType))
notification = notifications.get(eventType);
else
{
notification = new Notification(eventType);
notifications.put(eventType, notification);
}
notification.addAction(action);
// We fire the appropriate event depending on whether this is an
// already existing actionType or a new one.
fireNotificationActionTypeEvent(
isNew
? ACTION_ADDED
: ACTION_CHANGED,
eventType,
action);
}
// now store this default events if we want to restore them
Notification notification = null;
if(defaultNotifications.containsKey(eventType))
notification = defaultNotifications.get(eventType);
else
{
notification = new Notification(eventType);
defaultNotifications.put(eventType, notification);
}
notification.addAction(action);
}
/**
* Creates a new default EventNotification or obtains the corresponding
* existing one and registers a new action in it.
*
* @param eventType the name of the event (as defined by the plugin that's
* registering it) that we are setting an action for.
* @param actionType the type of the action that is to be executed when the
* specified event occurs (could be one of the ACTION_XXX fields).
* @param actionDescriptor a String containing a description of the action
* (a URI to the sound file for audio notifications or a command line for
* exec action types) that should be executed when the action occurs.
* @param defaultMessage the default message to use if no specific message
* has been provided when firing the notification.
*/
public void registerDefaultNotificationForEvent( String eventType,
String actionType,
String actionDescriptor,
String defaultMessage)
{
if (logger.isDebugEnabled())
logger.debug("Registering default event " + eventType + "/" +
actionType + "/" + actionDescriptor + "/" + defaultMessage);
if(isDefault(eventType, actionType))
{
NotificationAction action =
getEventNotificationAction(eventType, actionType);
boolean isNew = false;
if(action == null)
{
isNew = true;
if (actionType.equals(ACTION_SOUND))
{
action = new SoundNotificationAction(actionDescriptor, -1);
}
else if (actionType.equals(ACTION_LOG_MESSAGE))
{
action = new LogMessageNotificationAction(
LogMessageNotificationAction.INFO_LOG_TYPE);
}
else if (actionType.equals(ACTION_POPUP_MESSAGE))
{
action = new PopupMessageNotificationAction(defaultMessage);
}
else if (actionType.equals(ACTION_COMMAND))
{
action = new CommandNotificationAction(actionDescriptor);
}
}
this.saveNotification( eventType,
action,
action.isEnabled(),
true);
Notification notification = null;
if(notifications.containsKey(eventType))
notification = notifications.get(eventType);
else
{
notification = new Notification(eventType);
notifications.put(eventType, notification);
}
notification.addAction(action);
// We fire the appropriate event depending on whether this is an
// already existing actionType or a new one.
fireNotificationActionTypeEvent(
isNew
? ACTION_ADDED
: ACTION_CHANGED,
eventType,
action);
}
// now store this default events if we want to restore them
Notification notification = null;
if(defaultNotifications.containsKey(eventType))
notification = defaultNotifications.get(eventType);
else
{
notification = new Notification(eventType);
defaultNotifications.put(eventType, notification);
}
NotificationAction action = null;
if (actionType.equals(ACTION_SOUND))
{
action = new SoundNotificationAction(actionDescriptor, -1);
}
else if (actionType.equals(ACTION_LOG_MESSAGE))
{
action = new LogMessageNotificationAction(
LogMessageNotificationAction.INFO_LOG_TYPE);
}
else if (actionType.equals(ACTION_POPUP_MESSAGE))
{
action = new PopupMessageNotificationAction(defaultMessage);
}
else if (actionType.equals(ACTION_COMMAND))
{
action = new CommandNotificationAction(actionDescriptor);
}
notification.addAction(action);
}
/**
* Deletes all registered events and actions
* and registers and saves the default events as current.
*/
public void restoreDefaults()
{
for (String eventType : new Vector(notifications.keySet()))
{
Notification notification = notifications.get(eventType);
for (String actionType
: new Vector(notification.getActions().keySet()))
removeEventNotificationAction(eventType, actionType);
removeEventNotification(eventType);
}
for (Map.Entry entry
: defaultNotifications.entrySet())
{
String eventType = entry.getKey();
Notification notification = entry.getValue();
for (NotificationAction action : notification.getActions().values())
registerNotificationForEvent(eventType, action);
}
}
}