/*
* RHQ Management Platform
* Copyright (C) 2005-2014 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.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.enterprise.server.event;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.sql.DataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.db.DatabaseType;
import org.rhq.core.db.DatabaseTypeFactory;
import org.rhq.core.db.H2DatabaseType;
import org.rhq.core.db.OracleDatabaseType;
import org.rhq.core.db.PostgresqlDatabaseType;
import org.rhq.core.db.SQLServerDatabaseType;
import org.rhq.core.domain.auth.Subject;
import org.rhq.core.domain.authz.Permission;
import org.rhq.core.domain.common.EntityContext;
import org.rhq.core.domain.criteria.EventCriteria;
import org.rhq.core.domain.event.Event;
import org.rhq.core.domain.event.EventDefinition;
import org.rhq.core.domain.event.EventSeverity;
import org.rhq.core.domain.event.EventSource;
import org.rhq.core.domain.event.composite.EventComposite;
import org.rhq.core.domain.util.PageControl;
import org.rhq.core.domain.util.PageList;
import org.rhq.core.util.jdbc.JDBCUtil;
import org.rhq.enterprise.server.RHQConstants;
import org.rhq.enterprise.server.alert.engine.AlertConditionCacheManagerLocal;
import org.rhq.enterprise.server.alert.engine.AlertConditionCacheStats;
import org.rhq.enterprise.server.authz.AuthorizationManagerLocal;
import org.rhq.enterprise.server.authz.PermissionException;
import org.rhq.enterprise.server.util.CriteriaQueryGenerator;
import org.rhq.enterprise.server.util.CriteriaQueryRunner;
/**
* Manager for Handling of {@link Event}s.
* @author Heiko W. Rupp
* @author Ian Springer
* @author Joseph Marques
*/
@Stateless
public class EventManagerBean implements EventManagerLocal, EventManagerRemote {
// NOTE: We need to do the fancy subselects to figure out the event def id, because the PC does not know the id's of
// metadata objects such as EventDefinition (ips, 02/20/08).
private static final String EVENT_SOURCE_INSERT_STMT = "INSERT INTO RHQ_Event_Source (id, event_def_id, resource_id, location) "
+ "SELECT %s, (SELECT id FROM RHQ_Event_Def WHERE name = ? AND resource_type_id = (SELECT id FROM RHQ_Resource_Type WHERE name = ? AND plugin = ?)), ?, ? FROM RHQ_Numbers WHERE i = 42 "
+ "AND NOT EXISTS (SELECT * FROM RHQ_Event_Source WHERE event_def_id = (SELECT id FROM RHQ_Event_Def WHERE name = ? AND resource_type_id = (SELECT id FROM RHQ_Resource_Type WHERE name = ? AND plugin = ?)) AND resource_id = ? AND location = ?)";
private static final String EVENT_SOURCE_INSERT_STMT_AUTOINC = "INSERT INTO RHQ_Event_Source (event_def_id, resource_id, location) "
+ "SELECT (SELECT id FROM RHQ_Event_Def WHERE name = ? AND resource_type_id = (SELECT id FROM RHQ_Resource_Type WHERE name = ? AND plugin = ?)), ?, ? FROM RHQ_Numbers WHERE i = 42 "
+ "AND NOT EXISTS (SELECT * FROM RHQ_Event_Source WHERE event_def_id = (SELECT id FROM RHQ_Event_Def WHERE name = ? AND resource_type_id = (SELECT id FROM RHQ_Resource_Type WHERE name = ? AND plugin = ?)) AND resource_id = ? AND location = ?)";
private static final String EVENT_INSERT_STMT = "INSERT INTO RHQ_Event (id, event_source_id, timestamp, severity, detail) "
+ "VALUES (%s, (SELECT id FROM RHQ_Event_Source WHERE event_def_id = (SELECT id FROM RHQ_Event_Def WHERE name = ? AND resource_type_id = (SELECT id FROM RHQ_Resource_Type WHERE name = ? AND plugin = ?)) AND resource_id = ? AND location = ?), ?, ?, ?)";
private static final String EVENT_INSERT_STMT_AUTOINC = "INSERT INTO RHQ_Event (event_source_id, timestamp, severity, detail) "
+ "VALUES ((SELECT id FROM RHQ_Event_Source WHERE event_def_id = (SELECT id FROM RHQ_Event_Def WHERE name = ? AND resource_type_id = (SELECT id FROM RHQ_Resource_Type WHERE name = ? AND plugin = ?)) AND resource_id = ? AND location = ?), ?, ?, ?)";
@PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME)
private EntityManager entityManager;
@javax.annotation.Resource(name = "RHQ_DS", mappedName = RHQConstants.DATASOURCE_JNDI_NAME)
private DataSource rhqDs;
@EJB
private AlertConditionCacheManagerLocal alertConditionCacheManager;
@EJB
private AuthorizationManagerLocal authorizationManager;
Log log = LogFactory.getLog(EventManagerBean.class);
@Override
public void addEventData(Map<EventSource, Set<Event>> events) {
if (events == null || events.size() == 0)
return;
String statementSql;
Connection conn = null;
PreparedStatement ps = null;
try {
conn = rhqDs.getConnection();
DatabaseType dbType = DatabaseTypeFactory.getDefaultDatabaseType();
if (dbType instanceof PostgresqlDatabaseType || dbType instanceof OracleDatabaseType
|| dbType instanceof H2DatabaseType) {
String nextvalSql = JDBCUtil.getNextValSql(conn, EventSource.TABLE_NAME);
statementSql = String.format(EVENT_SOURCE_INSERT_STMT, nextvalSql);
} else if (dbType instanceof SQLServerDatabaseType) {
statementSql = EVENT_SOURCE_INSERT_STMT_AUTOINC;
} else {
throw new IllegalArgumentException("Unknown database type, can't continue: " + dbType);
}
// First insert the "keys" (i.e. the EventSources).
ps = conn.prepareStatement(statementSql);
try {
for (EventSource eventSource : events.keySet()) {
int paramIndex = 1;
ps.setString(paramIndex++, eventSource.getEventDefinition().getName());
ps.setString(paramIndex++, eventSource.getEventDefinition().getResourceType().getName());
ps.setString(paramIndex++, eventSource.getEventDefinition().getResourceType().getPlugin());
ps.setInt(paramIndex++, eventSource.getResource().getId());
ps.setString(paramIndex++, eventSource.getLocation());
ps.setString(paramIndex++, eventSource.getEventDefinition().getName());
ps.setString(paramIndex++, eventSource.getEventDefinition().getResourceType().getName());
ps.setString(paramIndex++, eventSource.getEventDefinition().getResourceType().getPlugin());
ps.setInt(paramIndex++, eventSource.getResource().getId());
ps.setString(paramIndex++, eventSource.getLocation());
ps.addBatch();
}
ps.executeBatch();
} finally {
JDBCUtil.safeClose(ps);
}
if (dbType instanceof PostgresqlDatabaseType || dbType instanceof OracleDatabaseType
|| dbType instanceof H2DatabaseType) {
String nextvalSql = JDBCUtil.getNextValSql(conn, Event.TABLE_NAME);
statementSql = String.format(EVENT_INSERT_STMT, nextvalSql);
} else if (dbType instanceof SQLServerDatabaseType) {
statementSql = EVENT_INSERT_STMT_AUTOINC;
} else {
throw new IllegalArgumentException("Unknown database type, can't continue: " + dbType);
}
// Then insert the "values" (i.e. the Events).
ps = conn.prepareStatement(statementSql);
try {
for (EventSource eventSource : events.keySet()) {
Set<Event> eventData = events.get(eventSource);
for (Event event : eventData) {
int paramIndex = 1;
ps.setString(paramIndex++, eventSource.getEventDefinition().getName());
ps.setString(paramIndex++, eventSource.getEventDefinition().getResourceType().getName());
ps.setString(paramIndex++, eventSource.getEventDefinition().getResourceType().getPlugin());
ps.setInt(paramIndex++, eventSource.getResource().getId());
ps.setString(paramIndex++, eventSource.getLocation());
ps.setLong(paramIndex++, event.getTimestamp());
ps.setString(paramIndex++, event.getSeverity().toString());
String detail = dbType.getString(event.getDetail(), Event.DETAIL_MAX_LENGTH);
ps.setString(paramIndex++, detail);
ps.addBatch();
}
// We may have trimmed the event detail for storage reasons, but for alerting use the
// full, potentially larger detail string.
notifyAlertConditionCacheManager("addEventData", eventSource,
eventData.toArray(new Event[eventData.size()]));
}
ps.executeBatch();
} finally {
JDBCUtil.safeClose(ps);
}
} catch (Throwable t) {
// TODO what do we want to do here ?
log.warn("addEventData: Insert of events failed : " + t.getMessage());
if (t instanceof SQLException) {
SQLException e = (SQLException) t;
Exception e2 = e.getNextException();
if (e2 != null)
log.warn(" : " + e2.getMessage());
if (t.getCause() != null)
log.warn(" : " + t.getCause().getMessage());
}
} finally {
JDBCUtil.safeClose(conn);
}
}
private void notifyAlertConditionCacheManager(String callingMethod, EventSource source, Event... events) {
AlertConditionCacheStats stats = alertConditionCacheManager.checkConditions(source, events);
log.debug(callingMethod + ": " + stats.toString());
}
@Override
public int[] getEventCounts(Subject subject, int resourceId, long begin, long end, int numBuckets) {
int[] buckets = new int[numBuckets];
// TODO possibly rewrite query so that the db calculates the buckets (?)
List<EventComposite> events = findEventComposites(subject, EntityContext.forResource(resourceId), begin, end,
null, null, null, PageControl.getUnlimitedInstance());
long timeDiff = end - begin;
long timePerBucket = timeDiff / numBuckets;
for (EventComposite event : events) {
long evTime = event.getTimestamp().getTime();
evTime = evTime - begin;
int bucket = (int) (evTime / timePerBucket);
buckets[bucket]++;
}
return buckets;
}
@Override
@SuppressWarnings("unchecked")
public EventComposite getEventDetailForEventId(Subject subject, int eventId) throws EventException {
Query q = entityManager.createNamedQuery(Event.GET_DETAILS_FOR_EVENT_IDS);
List<Integer> eventIds = new ArrayList<Integer>(1);
eventIds.add(eventId);
q.setParameter("eventIds", eventIds);
List<EventComposite> composites = q.getResultList();
if (composites.size() == 1)
return composites.get(0);
else {
throw new EventException("No event found for eventId[" + eventId + "]");
}
}
@Override
@SuppressWarnings("unchecked")
public void deleteEventSourcesForDefinition(EventDefinition def) {
Query q = entityManager.createNamedQuery(EventSource.QUERY_BY_EVENT_DEFINITION);
q.setParameter("definition", def);
List<EventSource> sources = q.getResultList();
for (EventSource source : sources) {
entityManager.remove(source);
}
}
@Override
public int deleteEventsForContext(Subject subject, EntityContext context, List<Integer> eventIds) {
if (eventIds == null || eventIds.size() == 0) {
return 0; // nothing to delete, thus 0 were deleted
}
if (context.type == EntityContext.Type.Resource) {
if (authorizationManager.hasResourcePermission(subject, Permission.MANAGE_EVENTS, context.resourceId) == false) {
throw new PermissionException("User [" + subject.getName()
+ "] does not have permissions to delete events for resource[id=" + context.resourceId + "]");
}
} else if (context.type == EntityContext.Type.ResourceGroup) {
if (authorizationManager.hasGroupPermission(subject, Permission.MANAGE_EVENTS, context.groupId) == false) {
throw new PermissionException("User [" + subject.getName()
+ "] does not have permissions to delete events for resourceGroup[id=" + context.groupId + "]");
}
} else if (context.type == EntityContext.Type.AutoGroup) {
if (authorizationManager.canViewAutoGroup(subject, context.parentResourceId, context.resourceTypeId) == false) {
throw new PermissionException("User [" + subject.getName()
+ "] does not have permission to view event history for autoGroup[parentResourceId="
+ context.parentResourceId + ", resourceTypeId=" + context.resourceTypeId + "]");
}
}
Query q = entityManager.createNamedQuery(Event.DELETE_BY_EVENT_IDS);
q.setParameter("eventIds", eventIds);
int deletedCount = q.executeUpdate();
return deletedCount;
}
@Override
public int purgeEventsForContext(Subject subject, EntityContext context) {
if (context.type == EntityContext.Type.Resource) {
if (authorizationManager.hasResourcePermission(subject, Permission.MANAGE_EVENTS, context.resourceId) == false) {
throw new PermissionException("User [" + subject.getName()
+ "] does not have permissions to purge events for resource[id=" + context.resourceId + "]");
}
} else if (context.type == EntityContext.Type.ResourceGroup) {
if (authorizationManager.hasGroupPermission(subject, Permission.MANAGE_EVENTS, context.groupId) == false) {
throw new PermissionException("User [" + subject.getName()
+ "] does not have permissions to purge events for resourceGroup[id=" + context.groupId + "]");
}
} else {
throw new IllegalArgumentException(context.getUnknownContextMessage());
}
Query purgeQuery = null;
if (context.type == EntityContext.Type.Resource) {
purgeQuery = entityManager.createNamedQuery(Event.DELETE_ALL_BY_RESOURCE);
purgeQuery.setParameter("resourceId", context.resourceId);
} else if (context.type == EntityContext.Type.ResourceGroup) {
purgeQuery = entityManager.createNamedQuery(Event.DELETE_ALL_BY_RESOURCE_GROUP);
purgeQuery.setParameter("groupId", context.groupId);
}
int deletedCount = purgeQuery.executeUpdate();
return deletedCount;
}
@Override
@SuppressWarnings("unchecked")
public Map<EventSeverity, Integer> getEventCountsBySeverity(Subject subject, int resourceId, long startDate,
long endDate) {
Map<EventSeverity, Integer> results = new HashMap<EventSeverity, Integer>();
Query q = entityManager.createNamedQuery(Event.QUERY_EVENT_COUNTS_BY_SEVERITY);
q.setParameter("resourceId", resourceId);
q.setParameter("start", startDate);
q.setParameter("end", endDate);
List<Object[]> rawResults = q.getResultList();
for (Object[] rawResult : rawResults) {
EventSeverity severity = (EventSeverity) rawResult[0];
long count = (Long) rawResult[1];
results.put(severity, (int) count);
}
return results;
}
@Override
@SuppressWarnings("unchecked")
public Map<EventSeverity, Integer> getEventCountsBySeverityForGroup(Subject subject, int groupId, long startDate,
long endDate) {
Map<EventSeverity, Integer> results = new HashMap<EventSeverity, Integer>();
Query q = entityManager.createNamedQuery(Event.QUERY_EVENT_COUNTS_BY_SEVERITY_GROUP);
q.setParameter("groupId", groupId);
q.setParameter("start", startDate);
q.setParameter("end", endDate);
List<Object[]> rawResults = q.getResultList();
for (Object[] rawResult : rawResults) {
EventSeverity severity = (EventSeverity) rawResult[0];
long count = (Long) rawResult[1];
results.put(severity, (int) count);
}
return results;
}
@Override
public EventSeverity[] getSeverityBucketsByContext(Subject subject, EntityContext context, long begin, long end,
int bucketCount) {
EventCriteria criteria = new EventCriteria();
criteria.addFilterStartTime(begin);
criteria.addFilterEndTime(end);
/*
* if the bucket computation is pushed into the database, it saves on data transfer across the wire. this
* solution is currently querying N number of strings (event.severity) and N number of longs (event.timestamp),
* where N is the number of events between 'begin' and 'end'. if the severity buckets are computed in a single
* query, the wire load would only be K integers, where K is the bucketCount.
*/
CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria);
;
String replacementSelectList = " event.severity, event.timestamp ";
generator.alterProjection(replacementSelectList);
if (authorizationManager.isInventoryManager(subject) == false) {
generator.setAuthorizationResourceFragment(CriteriaQueryGenerator.AuthorizationTokenType.RESOURCE,
"source.resource", subject.getId());
}
CriteriaQueryRunner<Object[]> queryRunner = new CriteriaQueryRunner<Object[]>(criteria, generator,
entityManager);
PageList<Object[]> flyWeights = queryRunner.execute();
EventSeverity[] buckets = new EventSeverity[bucketCount];
long timeDiff = end - begin;
long timePerBucket = timeDiff / bucketCount;
for (Object[] nextFly : flyWeights) {
long eventTime = (Long) nextFly[1];
EventSeverity eventSeverity = (EventSeverity) nextFly[0];
eventTime = eventTime - begin;
int bucket = (int) (eventTime / timePerBucket);
if (eventSeverity.isMoreSevereThan(buckets[bucket])) {
buckets[bucket] = eventSeverity;
}
}
return buckets;
}
@Override
public PageList<EventComposite> findEventComposites(Subject subject, EntityContext context, long begin, long end,
EventSeverity[] severities, String source, String detail, PageControl pc) {
if (context.type == EntityContext.Type.Resource) {
if (authorizationManager.canViewResource(subject, context.resourceId) == false) {
throw new PermissionException("User [" + subject.getName()
+ "] does not have permission to view event history for resource[id=" + context.resourceId + "]");
}
} else if (context.type == EntityContext.Type.ResourceGroup) {
if (authorizationManager.canViewGroup(subject, context.groupId) == false) {
throw new PermissionException("User [" + subject.getName()
+ "] does not have permission to view event history for resourceGroup[id=" + context.groupId + "]");
}
} else if (context.type == EntityContext.Type.AutoGroup) {
if (authorizationManager.canViewAutoGroup(subject, context.parentResourceId, context.resourceTypeId) == false) {
throw new PermissionException("User [" + subject.getName()
+ "] does not have permission to view event history for autoGroup[parentResourceId="
+ context.parentResourceId + ", resourceTypeId=" + context.resourceTypeId + "]");
}
}
EventCriteria criteria = new EventCriteria();
criteria.addFilterStartTime(begin);
criteria.addFilterEndTime(end);
criteria.addFilterSeverities(severities);
if (source != null && !source.trim().equals("")) {
criteria.addFilterSourceName(source);
}
if (detail != null && !detail.trim().equals("")) {
criteria.addFilterDetail(detail);
}
criteria.setPageControl(pc);
if (context.type == EntityContext.Type.Resource) {
criteria.addFilterResourceId(context.resourceId);
} else if (context.type == EntityContext.Type.ResourceGroup) {
criteria.addFilterResourceGroupId(context.groupId);
} else if (context.type == EntityContext.Type.AutoGroup) {
criteria.addFilterAutoGroupParentResourceId(context.parentResourceId);
criteria.addFilterAutoGroupResourceTypeId(context.resourceTypeId);
}
return findEventCompositesByCriteria(subject, criteria);
}
@Override
public PageList<EventComposite> findEventCompositesByCriteria(Subject subject, EventCriteria criteria) {
CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria);
String replacementSelectList = "" //
+ " new org.rhq.core.domain.event.composite.EventComposite( " //
+ " event.detail," //
+ " event.source.resource.id," //
+ " event.source.resource.name," //
+ " event.source.resource.ancestry," //
+ " event.source.resource.resourceType.id," //
+ " event.id," //
+ " event.severity," //
+ " event.source.location," //
+ " event.timestamp ) ";
generator.alterProjection(replacementSelectList);
if (authorizationManager.isInventoryManager(subject) == false) {
generator.setAuthorizationResourceFragment(CriteriaQueryGenerator.AuthorizationTokenType.RESOURCE,
"source.resource", subject.getId());
}
// log.info(generator.getParameterReplacedQuery(false));
// log.info(generator.getParameterReplacedQuery(true));
CriteriaQueryRunner<EventComposite> queryRunner = new CriteriaQueryRunner<EventComposite>(criteria, generator,
entityManager);
return queryRunner.execute();
}
@Override
@SuppressWarnings("unchecked")
public PageList<Event> findEventsByCriteria(Subject subject, EventCriteria criteria) {
CriteriaQueryGenerator generator = new CriteriaQueryGenerator(subject, criteria);
;
if (authorizationManager.isInventoryManager(subject) == false) {
generator.setAuthorizationResourceFragment(CriteriaQueryGenerator.AuthorizationTokenType.RESOURCE,
"source.resource", subject.getId());
}
CriteriaQueryRunner<Event> queryRunner = new CriteriaQueryRunner(criteria, generator, entityManager);
return queryRunner.execute();
}
/*
* Methods kept around because they are part of the remote interface, but all should be treated as deprecated
*/
@Override
public EventSeverity[] getSeverityBuckets(Subject subject, int resourceId, long begin, long end, int numBuckets) {
return getSeverityBucketsByContext(subject, EntityContext.forResource(resourceId), begin, end, numBuckets);
}
@Override
public EventSeverity[] getSeverityBucketsForAutoGroup(Subject subject, int parentResourceId, int resourceTypeId,
long begin, long end, int numBuckets) {
return getSeverityBucketsByContext(subject, EntityContext.forAutoGroup(parentResourceId, resourceTypeId),
begin, end, numBuckets);
}
@Override
public EventSeverity[] getSeverityBucketsForCompGroup(Subject subject, int resourceGroupId, long begin, long end,
int numBuckets) {
return getSeverityBucketsByContext(subject, EntityContext.forGroup(resourceGroupId), begin, end, numBuckets);
}
}