/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* 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 version 2 of the License.
*
* 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., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.alert;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.clientapi.agent.metadata.ConfigurationMetadataParser;
import org.rhq.core.clientapi.descriptor.configuration.ConfigurationDescriptor;
import org.rhq.core.domain.alert.AlertDefinition;
import org.rhq.core.domain.alert.AlertDefinitionContext;
import org.rhq.core.domain.alert.notification.AlertNotification;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.configuration.AbstractPropertyMap;
import org.rhq.core.domain.configuration.Property;
import org.rhq.core.domain.configuration.PropertyList;
import org.rhq.core.domain.configuration.PropertyMap;
import org.rhq.core.domain.configuration.PropertySimple;
import org.rhq.core.domain.configuration.definition.ConfigurationDefinition;
import org.rhq.core.domain.plugin.PluginKey;
import org.rhq.enterprise.server.RHQConstants;
import org.rhq.enterprise.server.auth.SubjectManagerLocal;
import org.rhq.enterprise.server.plugin.ServerPluginManagerLocal;
import org.rhq.enterprise.server.plugin.pc.alert.AlertSender;
import org.rhq.enterprise.server.plugin.pc.alert.AlertSenderInfo;
import org.rhq.enterprise.server.plugin.pc.alert.AlertSenderPluginManager;
import org.rhq.enterprise.server.plugin.pc.alert.AlertSenderValidationResults;
import org.rhq.enterprise.server.plugin.pc.alert.CustomAlertSenderBackingBean;
import org.rhq.enterprise.server.xmlschema.generated.serverplugin.alert.AlertPluginDescriptorType;
/**
* @author Joseph Marques
* @author Heiko W. Rupp
*/
@Stateless
public class AlertNotificationManagerBean implements AlertNotificationManagerLocal {
private static final Log LOG = LogFactory.getLog(AlertNotificationManagerBean.class);
@EJB
private AlertDefinitionManagerLocal alertDefinitionManager;
@EJB
private AlertTemplateManagerLocal alertTemplateManager;
@EJB
private AlertManagerLocal alertManager;
@EJB
private GroupAlertDefinitionManagerLocal groupAlertDefintionManager;
@EJB
private SubjectManagerLocal subjectManager;
@EJB
private ServerPluginManagerLocal serverPluginsBean;
@PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME)
private EntityManager entityManager;
/*
* Must use detached object in all circumstances where the AlertDefinition will eventually be passed to
* AlertDefinitionManager.updateAlertDefinition() to perform the actual modifications and persistence.
* If we use an attached AlertDefinity entity at this layer, modify the set of notifications, then call
* into AlertDefinitionManager.updateAlertDefinition() which executes in a new transaction, the work will
* actually be performed twice - once at each layer. This would result in either duplicate notifications
* (in the case of adding notifications) or Hibernate exceptions (in the case of removing notifications,
* which are attempted twice for each being removed).
*
* However, instead of loading the alertDefinition from within a transaction, and then detaching it from
* the Hibernate session, a better method is to just execute the add/removal logic for the AlertNotification(s)
* outside a transaction and then call AlertDefinitionManager.updateAlertNotification() which starts a new
* transaction to do all of its logic.
*
* Note: for AlertNotification updates to work properly, alertDefinitionManager.getAlertDefinitionById() must
* eagerly load the List<AlertNotification> on the returned AlertDefinition
*/
private AlertDefinition getDetachedAlertDefinition(int alertDefinitionId) {
AlertDefinition alertDefinition = alertDefinitionManager.getAlertDefinitionById(subjectManager.getOverlord(),
alertDefinitionId);
return alertDefinition;
}
/**
* @throws AlertDefinitionUpdateException if the {@link AlertNotification} is not associated with a known sender
*/
@Deprecated
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public AlertNotification addAlertNotification(Subject user, int alertDefinitionId, AlertNotification notification)
throws AlertDefinitionUpdateException {
List<String> validSenders = listAllAlertSenders();
if (validSenders.contains(notification.getSenderName()) == false) {
throw new AlertDefinitionUpdateException(notification.getSenderName()
+ " is not a valid alert sender, options are: " + validSenders);
}
AlertDefinition definition = getDetachedAlertDefinition(alertDefinitionId);
List<AlertNotification> notifications = definition.getAlertNotifications();
notifications.add(notification);
postProcessAlertDefinition(user, definition);
return notification;
}
@Deprecated
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public void updateAlertNotification(Subject subject, int alertDefinitionId, AlertNotification notification) {
AlertDefinition alertDefinition = getDetachedAlertDefinition(alertDefinitionId); // permissions check first
/*
* NULL notifications used to perform cascade updates from template and group level for alert senders
* that leverage custom UIs, which have a completely external methodology for loading/saving the data
* into and out of configuration object(s) associated with an AlertNotification.
*/
if (notification != null) {
// remove then add is a cheap way of performing an update
List<AlertNotification> notifications = alertDefinition.getAlertNotifications();
notifications.remove(notification);
notifications.add(notification);
}
postProcessAlertDefinition(subject, alertDefinition);
}
@Deprecated
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public int removeNotifications(Subject subject, Integer alertDefinitionId, Integer[] notificationIds) {
AlertDefinition alertDefinition = getDetachedAlertDefinition(alertDefinitionId); // permissions check first
if ((notificationIds == null) || (notificationIds.length == 0)) {
return 0;
}
Set<Integer> notificationIdSet = new HashSet<Integer>(Arrays.asList(notificationIds));
List<AlertNotification> notifications = alertDefinition.getAlertNotifications();
List<AlertNotification> toBeRemoved = new ArrayList<AlertNotification>();
int removed = 0;
for (AlertNotification notification : notifications) {
if (notificationIdSet.contains(notification.getId())) {
toBeRemoved.add(notification);
removed--;
}
}
alertDefinition.getAlertNotifications().removeAll(toBeRemoved);
postProcessAlertDefinition(subject, alertDefinition);
return removed;
}
public int purgeOrphanedAlertNotifications() {
Query purgeQuery = entityManager.createNamedQuery(AlertNotification.QUERY_DELETE_ORPHANED);
return purgeQuery.executeUpdate();
}
private AlertDefinition postProcessAlertDefinition(Subject subject, AlertDefinition definition) {
AlertDefinition updated = null;
AlertDefinitionContext context = definition.getContext();
if (context == AlertDefinitionContext.Type) {
updated = alertTemplateManager.updateAlertTemplate(subject, definition, true);
} else if (context == AlertDefinitionContext.Group) {
updated = groupAlertDefintionManager.updateGroupAlertDefinitions(subject, definition, true);
} else if (context == AlertDefinitionContext.Resource) {
updated = alertDefinitionManager.updateAlertDefinition(subject, definition.getId(), definition, false);
} else {
throw new IllegalStateException("No support for updating alert notifications for AlertDefinitionContext: "
+ definition.getContext());
}
return updated;
}
public ConfigurationDefinition getConfigurationDefinitionForSender(String shortName) {
AlertSenderPluginManager pluginmanager = alertManager.getAlertPluginManager();
AlertSenderInfo senderInfo = pluginmanager.getAlertSenderInfo(shortName);
String pluginName = senderInfo.getPluginName();
PluginKey key = senderInfo.getPluginKey();
try {
AlertPluginDescriptorType descriptor = (AlertPluginDescriptorType) serverPluginsBean
.getServerPluginDescriptor(key);
//ConfigurationDefinition pluginConfigurationDefinition = ConfigurationMetadataParser.parse("pc:" + pluginName, descriptor.getPluginConfiguration());
ConfigurationDescriptor alertConfiguration = descriptor.getAlertConfiguration();
if (alertConfiguration==null || alertConfiguration.getConfigurationProperty()== null || alertConfiguration.getConfigurationProperty().isEmpty()) {
// User either provided no <alert-configuration> or an empty one
return new ConfigurationDefinition("alerts:"+pluginName,"No properties given");
}
else {
ConfigurationDefinition pluginConfigurationDefinition = ConfigurationMetadataParser.parse("alerts:"
+ pluginName, alertConfiguration);
return pluginConfigurationDefinition;
}
} catch (Exception e) {
LOG.error(e);
return null;
}
}
/**
* Return a list of all available AlertSenders in the system by their shortname.
* @return list of senders.
*/
public List<String> listAllAlertSenders() {
AlertSenderPluginManager pluginmanager = alertManager.getAlertPluginManager();
List<String> senders = pluginmanager.getPluginList();
return senders;
}
public AlertSenderInfo getAlertInfoForSender(String shortName) {
AlertSenderPluginManager pluginmanager = alertManager.getAlertPluginManager();
AlertSenderInfo info = pluginmanager.getAlertSenderInfo(shortName);
return info;
}
/**
* Return the backing bean for the AlertSender with the passed shortName. If a notificationId is passed,
* we try to load the configuration for this notification and pass it to the CustomAlertSenderBackingBean instance
* @param shortName name of a sender
* @param alertNotificationId id of the notification we assign this sender + its backing bean to
* @return an initialized BackingBean or null in case of error
*/
public CustomAlertSenderBackingBean getBackingBeanForSender(String shortName, Integer alertNotificationId) {
AlertSenderPluginManager pluginmanager = alertManager.getAlertPluginManager();
CustomAlertSenderBackingBean bean = pluginmanager.getBackingBeanForSender(shortName);
if (alertNotificationId != null) {
AlertNotification notification = entityManager.find(AlertNotification.class, alertNotificationId);
if (notification != null && bean != null) {
bean.setAlertParameters(notification.getConfiguration().deepCopy(true));
if (notification.getExtraConfiguration() != null) {
bean.setExtraParameters(notification.getExtraConfiguration().deepCopy(true));
}
}
}
return bean;
}
public String getBackingBeanNameForSender(String shortName) {
AlertSenderPluginManager pluginmanager = alertManager.getAlertPluginManager();
return pluginmanager.getBackingBeanNameForSender(shortName);
}
/**
* Return notifications for a certain alertDefinitionId
*
* NOTE: this only returns notifications that have an AlertSender defined.
*
* @param user Subject of the caller
* @param alertDefinitionId Id of the alert definition
* @return list of defined notification of the passed alert definition
*
*
*/
public List<AlertNotification> getNotificationsForAlertDefinition(Subject user, int alertDefinitionId) {
AlertDefinition definition = alertDefinitionManager.getAlertDefinition(user, alertDefinitionId);
if (definition == null) {
LOG.error("Did not find definition for id [" + alertDefinitionId + "]");
return new ArrayList<AlertNotification>();
}
List<AlertNotification> notifications = definition.getAlertNotifications();
for (AlertNotification notification : notifications) {
notification.getConfiguration().getProperties().size(); // eager load
if (notification.getExtraConfiguration() != null) {
notification.getExtraConfiguration().getProperties().size();
}
}
return notifications;
}
public AlertNotification getAlertNotification(Subject user, int alertNotificationId) {
AlertNotification notification = entityManager.find(AlertNotification.class, alertNotificationId);
if (notification == null) {
return null;
}
notification.getConfiguration().getProperties().size(); // eager load the alert properties
if (notification.getExtraConfiguration() != null) {
notification.getExtraConfiguration().getProperties().size(); // eager load the extra alert properties
}
return notification;
}
public boolean finalizeNotifications(Subject subject, List<AlertNotification> notifications) {
boolean hasErrors = false;
if (notifications != null && notifications.size() > 0) {
AlertSenderPluginManager pluginManager = alertManager.getAlertPluginManager();
for (AlertNotification notification : notifications) {
AlertSender<?> sender = pluginManager.getAlertSenderForNotification(notification);
if (sender != null) {
AlertSenderValidationResults validation = sender.validateAndFinalizeConfiguration(subject);
notification.setConfiguration(validation.getAlertParameters());
notification.setExtraConfiguration(validation.getExtraParameters());
hasErrors = hasErrors || hasErrors(validation.getAlertParameters())
|| hasErrors(validation.getExtraParameters());
}
}
}
return !hasErrors;
}
public int cleanseAlertNotificationBySubject(int subjectId) {
return cleanseParameterValueForAlertSender("System Users", "subjectId", String.valueOf(subjectId));
}
public int cleanseAlertNotificationByRole(int roleId) {
return cleanseParameterValueForAlertSender("System Roles", "roleId", String.valueOf(roleId));
}
public void massReconfigure(List<Integer> alertNotificationIds, Map<String, String> newConfigurationValues) {
Query query = entityManager.createNamedQuery(AlertNotification.QUERY_UPDATE_PARAMETER_FOR_NOTIFICATIONS);
query.setParameter("alertNotificationIds", alertNotificationIds);
for (Map.Entry<String, String> entry : newConfigurationValues.entrySet()) {
query.setParameter("propertyName", entry.getKey());
query.setParameter("propertyValue", entry.getValue());
query.executeUpdate();
}
}
private int cleanseParameterValueForAlertSender(String senderName, String propertyName, String valueToCleanse) {
Query query = entityManager.createNamedQuery(AlertNotification.QUERY_CLEANSE_PARAMETER_VALUE_FOR_ALERT_SENDER);
query.setParameter("senderName", senderName);
query.setParameter("propertyName", propertyName);
query.setParameter("paramValue", "|" + valueToCleanse + "|"); // wrap with fence-delimiter for search
int affectedRows = query.executeUpdate();
return affectedRows;
}
private boolean hasErrors(AbstractPropertyMap configuration) {
if (configuration instanceof PropertyMap) {
if (((PropertyMap) configuration).getErrorMessage() != null) {
return true;
}
}
for (Map.Entry<String, Property> entry : configuration.getMap().entrySet()) {
if (hasErrors(entry.getValue())) {
return true;
}
}
return false;
}
private boolean hasErrors(PropertyList list) {
if (list.getErrorMessage() != null) {
return true;
}
for (Property p : list.getList()) {
if (hasErrors(p)) {
return true;
}
}
return false;
}
private boolean hasErrors(Property property) {
if (property instanceof PropertySimple) {
return property.getErrorMessage() != null;
} else if (property instanceof PropertyList) {
return hasErrors((PropertyList) property);
} else if (property instanceof PropertyMap) {
return hasErrors((AbstractPropertyMap) property);
} else {
return false;
}
}
}