/***************************************************************************** * * Copyright (C) Zenoss, Inc. 2010-2012, 2014 all rights reserved. * * This content is made available according to terms specified in * License.zenoss under the directory where your Zenoss product is installed. * ****************************************************************************/ package org.zenoss.zep.dao.impl; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.annotation.Timed; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.dao.DuplicateKeyException; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapperResultSetExtractor; import org.springframework.jdbc.core.simple.SimpleJdbcInsert; import org.springframework.jdbc.core.simple.SimpleJdbcOperations; import org.springframework.jdbc.support.MetaDataAccessException; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.StringUtils; import org.zenoss.protobufs.JsonFormat; import org.zenoss.protobufs.model.Model.ModelElementType; import org.zenoss.protobufs.zep.Zep.Event; import org.zenoss.protobufs.zep.Zep.EventActor; import org.zenoss.protobufs.zep.Zep.EventAuditLog; import org.zenoss.protobufs.zep.Zep.EventDetail; import org.zenoss.protobufs.zep.Zep.EventDetailSet; import org.zenoss.protobufs.zep.Zep.EventNote; import org.zenoss.protobufs.zep.Zep.EventSeverity; import org.zenoss.protobufs.zep.Zep.EventStatus; import org.zenoss.protobufs.zep.Zep.EventSummary; import org.zenoss.protobufs.zep.Zep.EventSummaryOrBuilder; import org.zenoss.protobufs.zep.Zep.EventTag; import org.zenoss.zep.Counters; import org.zenoss.zep.UUIDGenerator; import org.zenoss.zep.ZepConstants; import org.zenoss.zep.ZepException; import org.zenoss.zep.annotations.TransactionalReadOnly; import org.zenoss.zep.annotations.TransactionalRollbackAllExceptions; import org.zenoss.zep.dao.EventBatch; import org.zenoss.zep.dao.EventBatchParams; import org.zenoss.zep.dao.EventSummaryDao; import org.zenoss.zep.dao.impl.compat.DatabaseCompatibility; import org.zenoss.zep.dao.impl.compat.DatabaseType; import org.zenoss.zep.dao.impl.compat.NestedTransactionCallback; import org.zenoss.zep.dao.impl.compat.NestedTransactionContext; import org.zenoss.zep.dao.impl.compat.NestedTransactionService; import org.zenoss.zep.dao.impl.compat.TypeConverter; import org.zenoss.zep.dao.impl.compat.TypeConverterUtils; import org.zenoss.zep.index.WorkQueue; import org.zenoss.zep.index.impl.EventIndexBackendTask; import org.zenoss.zep.plugins.EventPreCreateContext; import javax.sql.DataSource; import java.io.IOException; import java.lang.reflect.Proxy; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import static org.zenoss.zep.dao.impl.EventConstants.*; public class EventSummaryDaoImpl implements EventSummaryDao { private static final Logger logger = LoggerFactory.getLogger(EventSummaryDaoImpl.class); @Autowired protected MetricRegistry metricRegistry; private boolean txSynchronizedQueue = true; private final ConcurrentMap<String, List<Event>> deduping = new ConcurrentHashMap<String, List<Event>>(); private final DataSource dataSource; private final SimpleJdbcOperations template; private final SimpleJdbcInsert insert; private volatile List<String> archiveColumnNames; private EventDaoHelper eventDaoHelper; private UUIDGenerator uuidGenerator; private DatabaseCompatibility databaseCompatibility; private TypeConverter<String> uuidConverter; private NestedTransactionService nestedTransactionService; private RowMapper<EventSummary.Builder> eventDedupMapper; private Counters counters; private WorkQueue eventIndexQueue; public EventSummaryDaoImpl(DataSource dataSource) throws MetaDataAccessException { this.dataSource = dataSource; this.template = (SimpleJdbcOperations) Proxy.newProxyInstance(SimpleJdbcOperations.class.getClassLoader(), new Class<?>[]{SimpleJdbcOperations.class}, new SimpleJdbcTemplateProxy(dataSource)); this.insert = new SimpleJdbcInsert(dataSource).withTableName(TABLE_EVENT_SUMMARY); } public void setEventDaoHelper(EventDaoHelper eventDaoHelper) { this.eventDaoHelper = eventDaoHelper; } public void setUuidGenerator(UUIDGenerator uuidGenerator) { this.uuidGenerator = uuidGenerator; } public void setDatabaseCompatibility(final DatabaseCompatibility databaseCompatibility) { this.databaseCompatibility = databaseCompatibility; this.uuidConverter = databaseCompatibility.getUUIDConverter(); // When we perform de-duping of events, we select a subset of just the fields we care about to determine // the de-duping behavior (depending on the timestamps on the event, we may perform merging or update either // the first_seen or last_seen dates appropriately). This mapper converts the subset of fields to an // EventSummaryOrBuilder object which has convenient accessor methods to retrieve the fields by name. this.eventDedupMapper = new RowMapper<EventSummary.Builder>() { @Override public EventSummary.Builder mapRow(ResultSet rs, int rowNum) throws SQLException { final TypeConverter<Long> timestampConverter = databaseCompatibility.getTimestampConverter(); final EventSummary.Builder oldSummaryBuilder = EventSummary.newBuilder(); oldSummaryBuilder.setCount(rs.getInt(COLUMN_EVENT_COUNT)); oldSummaryBuilder.setFirstSeenTime(timestampConverter.fromDatabaseType(rs, COLUMN_FIRST_SEEN)); oldSummaryBuilder.setLastSeenTime(timestampConverter.fromDatabaseType(rs, COLUMN_LAST_SEEN)); oldSummaryBuilder.setStatus(EventStatus.valueOf(rs.getInt(COLUMN_STATUS_ID))); oldSummaryBuilder.setStatusChangeTime(timestampConverter.fromDatabaseType(rs, COLUMN_STATUS_CHANGE)); oldSummaryBuilder.setUuid(uuidConverter.fromDatabaseType(rs, COLUMN_UUID)); final Event.Builder occurrenceBuilder = oldSummaryBuilder.addOccurrenceBuilder(0); final String detailsJson = rs.getString(COLUMN_DETAILS_JSON); if (detailsJson != null) { try { occurrenceBuilder.addAllDetails(JsonFormat.mergeAllDelimitedFrom(detailsJson, EventDetail.getDefaultInstance())); } catch (IOException e) { throw new SQLException(e.getLocalizedMessage(), e); } } return oldSummaryBuilder; } }; } public void setNestedTransactionService(NestedTransactionService nestedTransactionService) { this.nestedTransactionService = nestedTransactionService; } public void setCounters(final Counters counters) { this.counters = counters; } @Override public void setTxSynchronizedQueue(boolean val) { txSynchronizedQueue = val; } @Override @Timed @TransactionalRollbackAllExceptions public String create(Event event, final EventPreCreateContext context) throws ZepException { /* * Clear events are dropped if they don't clear any corresponding events. */ final List<String> clearedEventUuids; final boolean createClearHash; if (event.getSeverity() == EventSeverity.SEVERITY_CLEAR) { final Event finalEvent = event; try { clearedEventUuids = metricRegistry.timer(getClass().getName() + ".clearEvents").time(new Callable<List<String>>() { @Override public List<String> call() throws Exception { return clearEvents(finalEvent, context); } }); } catch (ZepException e) { throw e; } catch (Exception e) { throw new ZepException(e); } if (clearedEventUuids.isEmpty()) { logger.debug("Clear event didn't clear any events, dropping: {}", event); return null; } // Clear events always get created in CLOSED status if (event.getStatus() != EventStatus.STATUS_CLOSED) { event = Event.newBuilder(event).setStatus(EventStatus.STATUS_CLOSED).build(); } createClearHash = false; } else { createClearHash = true; clearedEventUuids = Collections.emptyList(); } /* * Closed events have a unique fingerprint_hash in summary to allow multiple rows * but only allow one active event (where the de-duplication occurs). */ final String fingerprint = DaoUtils.truncateStringToUtf8(event.getFingerprint(), MAX_FINGERPRINT); final byte[] fingerprintHash; final String uuid; if (ZepConstants.CLOSED_STATUSES.contains(event.getStatus())) { long ts; ts = System.currentTimeMillis(); // When the clear event and the event it clears happen to close is the same // millisecond, the fingerprint hashes will be the same and DuplicateKeyException is raised. // Subtracting 1 from the value here ensures that the same timestamp isn't used. if (event.getSeverity() == EventSeverity.SEVERITY_CLEAR) { ts = ts - 1; } fingerprintHash = DaoUtils.sha1(fingerprint + '|' + ts); uuid = saveEventByFingerprint(fingerprintHash, Collections.singleton(event), context, createClearHash); } else { fingerprintHash = DaoUtils.sha1(fingerprint); final String hashAsString = new String(fingerprintHash).intern(); final Event finalEvent = event; try { metricRegistry.timer(getClass().getName() + ".queueDedup").time(new Callable() { @Override public Object call() throws Exception { boolean queued = false; while (!queued) { List<Event> events = deduping.get(hashAsString); if (events == null) { deduping.putIfAbsent(hashAsString, Collections.EMPTY_LIST); continue; } List<Event> newEvents = Lists.newArrayList(events); newEvents.add(finalEvent); queued = deduping.replace(hashAsString, events, newEvents); } return null; } }); } catch (ZepException e) { throw e; } catch (Exception e) { throw new ZepException(e); } try { uuid = metricRegistry.timer(getClass().getName() + ".dedupSync").time(new Callable<String>() { @Override public String call() throws Exception { synchronized (hashAsString) { List<Event> events = deduping.remove(hashAsString); if (events == null) events = Collections.EMPTY_LIST; return saveEventByFingerprint(fingerprintHash, events, context, createClearHash); } } }); } catch (ZepException e) { throw e; } catch (Exception e) { throw new ZepException(e); } } if (uuid == null && !clearedEventUuids.isEmpty()) { // This only happens if another thread was processing the same dup and grabbed ours, // AND in the time between that thread leaving its critical section and this thread // querying for the event_summary by fingerprint_hash, the record we're interested in // got deleted (archived). // // Anyway, probably not a big deal. logger.info("Rare race condition thwarted update of clearedByEventUuid for {} events.", clearedEventUuids.size()); return null; } try { metricRegistry.timer(getClass().getName() + ".dedupClearEvents").time(new Callable<Object>() { @Override public Object call() throws Exception { // Mark cleared events as cleared by this event if (!clearedEventUuids.isEmpty()) { final EventSummaryUpdateFields updateFields = new EventSummaryUpdateFields(); updateFields.setClearedByEventUuid(uuid); update(clearedEventUuids, EventStatus.STATUS_CLEARED, updateFields, ZepConstants.OPEN_STATUSES); } return null; } }); } catch (ZepException e) { throw e; } catch (Exception e) { throw new ZepException(e); } return uuid; } private Map<String, Object> getInsertFields(EventSummaryOrBuilder summary, EventPreCreateContext context, boolean createClearHash) throws ZepException { TypeConverter<Long> timestampConverter = databaseCompatibility.getTimestampConverter(); Event event = summary.getOccurrence(0); final Map<String, Object> fields = eventDaoHelper.createOccurrenceFields(event); fields.put(COLUMN_STATUS_ID, summary.getStatus().getNumber()); fields.put(COLUMN_CLOSED_STATUS, ZepConstants.CLOSED_STATUSES.contains(summary.getStatus())); fields.put(COLUMN_UPDATE_TIME, timestampConverter.toDatabaseType(summary.getUpdateTime())); fields.put(COLUMN_FIRST_SEEN, timestampConverter.toDatabaseType(summary.getFirstSeenTime())); fields.put(COLUMN_STATUS_CHANGE, timestampConverter.toDatabaseType(summary.getStatusChangeTime())); fields.put(COLUMN_LAST_SEEN, timestampConverter.toDatabaseType(summary.getLastSeenTime())); fields.put(COLUMN_EVENT_COUNT, summary.getCount()); if (!summary.hasUuid() || summary.getUuid() == null) { throw new NullPointerException("missing uuid"); } fields.put(COLUMN_UUID, uuidConverter.toDatabaseType(summary.getUuid())); if (createClearHash) { fields.put(COLUMN_CLEAR_FINGERPRINT_HASH, EventDaoUtils.createClearHash(event, context.getClearFingerprintGenerator())); } return fields; } private String saveEventByFingerprint(final byte[] fingerprintHash, final Collection<Event> events, final EventPreCreateContext context, final boolean createClearHash) throws ZepException { try { return metricRegistry.timer(getClass().getName() + ".saveEventByFingerprint").time(new Callable<String>() { @Override public String call() throws Exception { final List<EventSummary.Builder> oldSummaryList = template.getJdbcOperations().query( "SELECT event_count,first_seen,last_seen,details_json,status_id,status_change,uuid" + " FROM event_summary WHERE fingerprint_hash=? FOR UPDATE", new RowMapperResultSetExtractor<EventSummary.Builder>(eventDedupMapper, 1), fingerprintHash); final EventSummary.Builder summary; if (!oldSummaryList.isEmpty()) { summary = oldSummaryList.get(0); } else { summary = EventSummary.newBuilder(); summary.setCount(0); summary.addOccurrenceBuilder(0); } boolean isNewer = false; for (Event event : events) { isNewer = merge(summary, event) || isNewer; } if (!events.isEmpty()) { summary.setUpdateTime(System.currentTimeMillis()); final long dedupCount; if (!oldSummaryList.isEmpty()) { dedupCount = events.size(); final Map<String, Object> fields = getUpdateFields(summary, isNewer, context, createClearHash); final StringBuilder updateSql = new StringBuilder("UPDATE event_summary SET "); int i = 0; for (String fieldName : fields.keySet()) { if (++i > 1) updateSql.append(','); updateSql.append(fieldName).append("=:").append(fieldName); } updateSql.append(" WHERE fingerprint_hash=:fingerprint_hash"); fields.put("fingerprint_hash", fingerprintHash); template.update(updateSql.toString(), fields); } else { dedupCount = events.size() - 1; summary.setUuid(uuidGenerator.generate().toString()); final Map<String, Object> fields = getInsertFields(summary, context, createClearHash); fields.put(COLUMN_FINGERPRINT_HASH, fingerprintHash); insert.execute(fields); } indexSignal(summary.getUuid()); if (dedupCount > 0) { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { counters.addToDedupedEventCount(dedupCount); } }); } } return summary.getUuid(); } }); } catch (ZepException e) { throw e; } catch (Exception e) { throw new ZepException(e); } } private boolean merge(EventSummary.Builder merged, Event occurrence) throws ZepException { boolean isNewer = false; merged.setCount(merged.getCount() + occurrence.getCount()); if (!merged.hasLastSeenTime() || occurrence.getCreatedTime() >= merged.getLastSeenTime()) { isNewer = true; merged.setLastSeenTime(occurrence.getCreatedTime()); Event.Builder ob = merged.getOccurrenceBuilder(0); EventActor.Builder ab = ob.getActorBuilder(); if (occurrence.hasEventGroup()) ob.setEventGroup(occurrence.getEventGroup()); if (occurrence.hasEventClass()) ob.setEventClass(occurrence.getEventClass()); if (occurrence.hasEventClassKey()) ob.setEventClassKey(occurrence.getEventClassKey()); if (occurrence.hasEventClassMappingUuid()) ob.setEventClassMappingUuid(occurrence.getEventClassMappingUuid()); if (occurrence.hasEventKey()) ob.setEventKey(occurrence.getEventKey()); if (occurrence.hasSeverity()) ob.setSeverity(occurrence.getSeverity()); if (occurrence.hasMonitor()) ob.setMonitor(occurrence.getMonitor()); if (occurrence.hasAgent()) ob.setAgent(occurrence.getAgent()); if (occurrence.hasSyslogFacility()) ob.setSyslogFacility(occurrence.getSyslogFacility()); if (occurrence.hasSyslogPriority()) ob.setSyslogPriority(occurrence.getSyslogPriority()); if (occurrence.hasNtEventCode()) ob.setNtEventCode(occurrence.getNtEventCode()); if (occurrence.hasSummary()) ob.setSummary(occurrence.getSummary()); if (occurrence.hasMessage()) ob.setMessage(occurrence.getMessage()); List<EventTag> tagsList = occurrence.getTagsList(); if (tagsList != null && !tagsList.isEmpty()) { ob.clearTags(); ob.addAllTags(tagsList); } if (!ob.hasFingerprint() || ob.getFingerprint() == null && occurrence.hasFingerprint()) { ob.setFingerprint(occurrence.getFingerprint()); } EventActor actor = occurrence.getActor(); if (actor != null) { if (actor.hasElementUuid()) ab.setElementUuid(actor.getElementUuid()); if (actor.hasElementTypeId()) ab.setElementTypeId(actor.getElementTypeId()); if (actor.hasElementIdentifier()) ab.setElementIdentifier(actor.getElementIdentifier()); if (actor.hasElementTitle()) ab.setElementTitle(actor.getElementTitle()); if (actor.hasElementSubUuid()) ab.setElementSubUuid(actor.getElementSubUuid()); if (actor.hasElementSubTypeId()) ab.setElementSubTypeId(actor.getElementSubTypeId()); if (actor.hasElementSubIdentifier()) ab.setElementSubIdentifier(actor.getElementSubIdentifier()); if (actor.hasElementSubTitle()) ab.setElementSubTitle(actor.getElementSubTitle()); } // Update status except for ACKNOWLEDGED -> {NEW|SUPPRESSED} // Stays in ACKNOWLEDGED in these cases boolean updateStatus = true; EventStatus oldStatus = merged.hasStatus() ? merged.getStatus() : null; EventStatus newStatus = occurrence.getStatus(); if (oldStatus == EventStatus.STATUS_ACKNOWLEDGED) { switch (newStatus) { case STATUS_NEW: case STATUS_SUPPRESSED: updateStatus = false; break; } } if (updateStatus && oldStatus != newStatus) { merged.setStatus(occurrence.getStatus()); merged.setStatusChangeTime(occurrence.getCreatedTime()); } if (!merged.hasStatusChangeTime()) { merged.setStatusChangeTime(occurrence.getCreatedTime()); } // Merge event details List<EventDetail> newDetails = occurrence.getDetailsList(); if (!newDetails.isEmpty()) { List<EventDetail> oldDetails = ob.getDetailsList(); if (oldDetails.isEmpty()) { ob.addAllDetails(newDetails); } else { ob.clearDetails(); String json = eventDaoHelper.mergeDetailsToJson(oldDetails, newDetails); try { ob.addAllDetails(JsonFormat.mergeAllDelimitedFrom(json, EventDetail.getDefaultInstance())); } catch (IOException e) { throw new ZepException(e); } } } } else { // This is the case where the event that we're processing is OLDER // than the last seen time on the summary. // Merge event details - order swapped b/c of out of order event List<EventDetail> oldDetails = occurrence.getDetailsList(); if (!oldDetails.isEmpty()) { Event.Builder ob = merged.getOccurrenceBuilder(0); List<EventDetail> newDetails = ob.getDetailsList(); if (newDetails.isEmpty()) { ob.addAllDetails(oldDetails); } else { ob.clearDetails(); String json = eventDaoHelper.mergeDetailsToJson(oldDetails, newDetails); try { ob.addAllDetails(JsonFormat.mergeAllDelimitedFrom(json, EventDetail.getDefaultInstance())); } catch (IOException e) { throw new ZepException(e); } } } } long firstSeen = occurrence.hasFirstSeenTime() ? occurrence.getFirstSeenTime() : occurrence.getCreatedTime(); if (!merged.hasFirstSeenTime() || firstSeen < merged.getFirstSeenTime()) { merged.setFirstSeenTime(firstSeen); } return isNewer; } /** * When an event is de-duped, if the event occurrence has a created time greater than or equal to the current * last_seen for the event summary, these fields from the event summary row are overwritten by values from the new * event occurrence. Special handling is performed when de-duping for event status and event details. */ private static final List<String> UPDATE_FIELD_NAMES = Arrays.asList(COLUMN_EVENT_GROUP_ID, COLUMN_EVENT_CLASS_ID, COLUMN_EVENT_CLASS_KEY_ID, COLUMN_EVENT_CLASS_MAPPING_UUID, COLUMN_EVENT_KEY_ID, COLUMN_SEVERITY_ID, COLUMN_ELEMENT_UUID, COLUMN_ELEMENT_TYPE_ID, COLUMN_ELEMENT_IDENTIFIER, COLUMN_ELEMENT_TITLE, COLUMN_ELEMENT_SUB_UUID, COLUMN_ELEMENT_SUB_TYPE_ID, COLUMN_ELEMENT_SUB_IDENTIFIER, COLUMN_ELEMENT_SUB_TITLE, COLUMN_LAST_SEEN, COLUMN_MONITOR_ID, COLUMN_AGENT_ID, COLUMN_SYSLOG_FACILITY, COLUMN_SYSLOG_PRIORITY, COLUMN_NT_EVENT_CODE, COLUMN_CLEAR_FINGERPRINT_HASH, COLUMN_SUMMARY, COLUMN_MESSAGE, COLUMN_TAGS_JSON); private Map<String, Object> getUpdateFields(EventSummaryOrBuilder summary, boolean isNewer, EventPreCreateContext context, boolean createClearHash) throws ZepException { TypeConverter<Long> timestampConverter = databaseCompatibility.getTimestampConverter(); Map<String, Object> fields = new HashMap<String, Object>(); Map<String, Object> insertFields = getInsertFields(summary, context, createClearHash); if (isNewer) { for (String fieldName : UPDATE_FIELD_NAMES) { fields.put(fieldName, insertFields.get(fieldName)); } fields.put(COLUMN_STATUS_ID, insertFields.get(COLUMN_STATUS_ID)); fields.put(COLUMN_CLOSED_STATUS, insertFields.get(COLUMN_CLOSED_STATUS)); fields.put(COLUMN_STATUS_CHANGE, timestampConverter.toDatabaseType(summary.getStatusChangeTime())); } fields.put(COLUMN_EVENT_COUNT, summary.getCount()); fields.put(COLUMN_UPDATE_TIME, timestampConverter.toDatabaseType(summary.getUpdateTime())); fields.put(COLUMN_DETAILS_JSON, insertFields.get(COLUMN_DETAILS_JSON)); fields.put(COLUMN_FIRST_SEEN, timestampConverter.toDatabaseType(summary.getFirstSeenTime())); return fields; } private List<String> clearEvents(Event event, EventPreCreateContext context) throws ZepException { TypeConverter<Long> timestampConverter = databaseCompatibility.getTimestampConverter(); final List<byte[]> clearHashes = EventDaoUtils.createClearHashes(event, context); if (clearHashes.isEmpty()) { logger.debug("Clear event didn't contain any clear hashes: {}, {}", event, context); return Collections.emptyList(); } final long lastSeen = event.getCreatedTime(); Map<String, Object> fields = new HashMap<String, Object>(2); fields.put("_clear_created_time", timestampConverter.toDatabaseType(lastSeen)); fields.put("_clear_hashes", clearHashes); /* Find events that this clear event would clear. */ final String sql = "SELECT uuid FROM event_summary " + "WHERE last_seen <= :_clear_created_time " + "AND clear_fingerprint_hash IN (:_clear_hashes) " + "AND closed_status = FALSE " + "FOR UPDATE"; final List<String> results = this.template.query(sql, new RowMapper<String>() { @Override public String mapRow(ResultSet rs, int rowNum) throws SQLException { return uuidConverter.fromDatabaseType(rs, COLUMN_UUID); } }, fields); indexSignal(results); TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { counters.addToClearedEventCount(results.size()); } }); return results; } public void setEventIndexQueue(WorkQueue eventIndexQueue) { this.eventIndexQueue = eventIndexQueue; } /** * When re-identifying or de-identifying events, we recalculate the clear_fingerprint_hash for the event to either * include (re-identify) or exclude (de-identify) the UUID of the sub_element. This mapper retrieves a subset of * fields for the event in order to recalculate the clear_fingerprint_hash. */ private static class IdentifyMapper implements RowMapper<Map<String, Object>> { private final Map<String, Object> fields; private final String elementSubUuid; public IdentifyMapper(Map<String, Object> fields, String elementSubUuid) { this.fields = Collections.unmodifiableMap(fields); this.elementSubUuid = elementSubUuid; } @Override public Map<String, Object> mapRow(ResultSet rs, int rowNum) throws SQLException { Map<String, Object> updateFields = new HashMap<String, Object>(fields); Event.Builder event = Event.newBuilder(); EventActor.Builder actor = event.getActorBuilder(); actor.setElementIdentifier(rs.getString(COLUMN_ELEMENT_IDENTIFIER)); String elementSubIdentifier = rs.getString(COLUMN_ELEMENT_SUB_IDENTIFIER); if (elementSubIdentifier != null) { actor.setElementSubIdentifier(elementSubIdentifier); } if (this.elementSubUuid != null) { actor.setElementSubUuid(this.elementSubUuid); } event.setEventClass(rs.getString("event_class_name")); String eventKey = rs.getString("event_key_name"); if (eventKey != null) { event.setEventKey(eventKey); } updateFields.put(COLUMN_UUID, rs.getObject(COLUMN_UUID)); updateFields.put(COLUMN_CLEAR_FINGERPRINT_HASH, EventDaoUtils.createClearHash(event.build())); return updateFields; } } @Override @TransactionalRollbackAllExceptions @Timed public int reidentify(ModelElementType type, String id, String uuid, String title, String parentUuid) throws ZepException { TypeConverter<Long> timestampConverter = databaseCompatibility.getTimestampConverter(); long updateTime = System.currentTimeMillis(); final Map<String, Object> fields = new HashMap<String, Object>(); fields.put("_uuid", uuidConverter.toDatabaseType(uuid)); fields.put("_uuid_str", uuid); fields.put("_type_id", type.getNumber()); fields.put("_id", id); fields.put("_title", DaoUtils.truncateStringToUtf8(title, EventConstants.MAX_ELEMENT_TITLE)); fields.put(COLUMN_UPDATE_TIME, timestampConverter.toDatabaseType(updateTime)); String indexSql = "SELECT uuid FROM event_summary " + "WHERE element_uuid IS NULL AND element_type_id=:_type_id AND element_identifier=:_id"; this.indexResults(indexSql, fields); int numRows = 0; String updateSql = "UPDATE event_summary SET element_uuid=:_uuid, element_title=:_title," + " update_time=:update_time WHERE element_uuid IS NULL AND element_type_id=:_type_id" + " AND element_identifier=:_id"; numRows += this.template.update(updateSql, fields); if (parentUuid != null) { fields.put("_parent_uuid", uuidConverter.toDatabaseType(parentUuid)); indexSql = "SELECT es.uuid " + "FROM event_summary es INNER JOIN event_class ON es.event_class_id = event_class.id " + "LEFT JOIN event_key ON es.event_key_id = event_key.id " + "WHERE es.element_uuid=:_parent_uuid AND es.element_sub_uuid IS NULL AND " + "es.element_sub_type_id=:_type_id AND es.element_sub_identifier=:_id"; this.indexResults(indexSql, fields); String selectSql = "SELECT uuid,element_identifier,element_sub_identifier," + "event_class.name AS event_class_name,event_key.name AS event_key_name FROM event_summary es" + " INNER JOIN event_class ON es.event_class_id = event_class.id" + " LEFT JOIN event_key on es.event_key_id = event_key.id" + " WHERE es.element_uuid=:_parent_uuid AND es.element_sub_uuid IS NULL" + " AND es.element_sub_type_id=:_type_id AND es.element_sub_identifier=:_id FOR UPDATE"; // MySQL locks all joined rows, PostgreSQL requires you to specify the rows from each table to lock if (this.databaseCompatibility.getDatabaseType() == DatabaseType.POSTGRESQL) { selectSql += " OF es"; } List<Map<String, Object>> updateFields = this.template.query(selectSql, new IdentifyMapper(fields, uuid), fields); String updateSubElementSql = "UPDATE event_summary SET element_sub_uuid=:_uuid, " + "element_sub_title=:_title, update_time=:update_time, " + "clear_fingerprint_hash=:clear_fingerprint_hash WHERE uuid=:uuid"; int[] updated = this.template.batchUpdate(updateSubElementSql, updateFields.toArray(new Map[updateFields.size()])); for (int updatedRows : updated) { numRows += updatedRows; } } return numRows; } @Override @TransactionalRollbackAllExceptions @Timed public int deidentify(String uuid) throws ZepException { TypeConverter<Long> timestampConverter = databaseCompatibility.getTimestampConverter(); long updateTime = System.currentTimeMillis(); final Map<String, Object> fields = new HashMap<String, Object>(2); fields.put("_uuid", uuidConverter.toDatabaseType(uuid)); fields.put(COLUMN_UPDATE_TIME, timestampConverter.toDatabaseType(updateTime)); String indexSql = "SELECT uuid FROM event_summary WHERE element_uuid=:_uuid"; this.indexResults(indexSql, fields); int numRows = 0; String updateElementSql = "UPDATE event_summary SET element_uuid=NULL, update_time=:update_time" + " WHERE element_uuid=:_uuid"; numRows += this.template.update(updateElementSql, fields); indexSql = "SELECT uuid FROM event_summary WHERE element_sub_uuid=:_uuid"; this.indexResults(indexSql, fields); String selectSql = "SELECT uuid,element_identifier,element_sub_identifier," + "event_class.name AS event_class_name,event_key.name AS event_key_name FROM event_summary es" + " INNER JOIN event_class ON es.event_class_id = event_class.id" + " LEFT JOIN event_key on es.event_key_id = event_key.id WHERE element_sub_uuid=:_uuid FOR UPDATE"; // MySQL locks all joined rows, PostgreSQL requires you to specify the rows from each table to lock if (this.databaseCompatibility.getDatabaseType() == DatabaseType.POSTGRESQL) { selectSql += " OF es"; } List<Map<String, Object>> updateFields = this.template.query(selectSql, new IdentifyMapper(fields, null), fields); String updateSubElementSql = "UPDATE event_summary SET element_sub_uuid=NULL, update_time=:update_time, " + "clear_fingerprint_hash=:clear_fingerprint_hash WHERE uuid=:uuid"; int[] updated = this.template.batchUpdate(updateSubElementSql, updateFields.toArray(new Map[updateFields.size()])); for (int updatedRows : updated) { numRows += updatedRows; } return numRows; } @Override @TransactionalReadOnly @Timed public EventSummary findByUuid(String uuid) throws ZepException { final Map<String, Object> fields = Collections.singletonMap(COLUMN_UUID, uuidConverter.toDatabaseType(uuid)); List<EventSummary> summaries = this.template.query("SELECT * FROM event_summary WHERE uuid=:uuid", new EventSummaryRowMapper(this.eventDaoHelper, this.databaseCompatibility), fields); return (summaries.size() > 0) ? summaries.get(0) : null; } @Override @Deprecated @Timed /** @deprecated use {@link #findByKey(Collection) instead}. */ public List<EventSummary> findByUuids(final List<String> uuids) throws ZepException { return findByUuids((Collection) uuids); } @TransactionalReadOnly private List<EventSummary> findByUuids(final Collection<String> uuids) throws ZepException { if (uuids.isEmpty()) { return Collections.emptyList(); } Map<String, List<Object>> fields = Collections.singletonMap("uuids", TypeConverterUtils.batchToDatabaseType(uuidConverter, uuids)); return this.template.query("SELECT * FROM event_summary WHERE uuid IN(:uuids)", new EventSummaryRowMapper(this.eventDaoHelper, this.databaseCompatibility), fields); } @Override @TransactionalReadOnly @Timed /** * This implementation only makes use of the UUID field to lookup the events. */ public List<EventSummary> findByKey(final Collection<EventSummary> toLookup) throws ZepException { if (toLookup == null || toLookup.isEmpty()) return Collections.emptyList(); Set<String> uuids = Sets.newHashSetWithExpectedSize(toLookup.size()); for (EventSummary event : toLookup) uuids.add(event.getUuid()); return findByUuids(uuids); } @Override @TransactionalReadOnly @Timed public EventBatch listBatch(EventBatchParams batchParams, long maxUpdateTime, int limit) throws ZepException { return this.eventDaoHelper.listBatch(this.template, TABLE_EVENT_SUMMARY, null, batchParams, maxUpdateTime, limit, new EventSummaryRowMapper(eventDaoHelper, databaseCompatibility)); } private static final EnumSet<EventStatus> AUDIT_LOG_STATUSES = EnumSet.of( EventStatus.STATUS_NEW, EventStatus.STATUS_ACKNOWLEDGED, EventStatus.STATUS_CLOSED, EventStatus.STATUS_CLEARED); private static List<Integer> getSeverityIds(EventSeverity maxSeverity, boolean inclusiveSeverity) { List<Integer> severityIds = EventDaoHelper.getSeverityIdsLessThan(maxSeverity); if (inclusiveSeverity) { severityIds.add(maxSeverity.getNumber()); } return severityIds; } @Override @Timed public long getAgeEligibleEventCount(long duration, TimeUnit unit, EventSeverity maxSeverity, boolean inclusiveSeverity) { List<Integer> severityIds = getSeverityIds(maxSeverity, inclusiveSeverity); // Aging disabled. if (severityIds.isEmpty()) { return 0; } String sql = "SELECT count(*) FROM event_summary WHERE closed_status = FALSE AND " + "last_seen < :_last_seen AND severity_id IN (:_severity_ids)"; Map<String, Object> fields = createSharedFields(duration, unit); fields.put("_severity_ids", severityIds); return template.queryForInt(sql, fields); } @Override @TransactionalRollbackAllExceptions @Timed public int ageEvents(long agingInterval, TimeUnit unit, EventSeverity maxSeverity, int limit, boolean inclusiveSeverity) throws ZepException { TypeConverter<Long> timestampConverter = databaseCompatibility.getTimestampConverter(); long agingIntervalMs = unit.toMillis(agingInterval); if (agingIntervalMs < 0 || agingIntervalMs == Long.MAX_VALUE) { throw new ZepException("Invalid aging interval: " + agingIntervalMs); } if (limit <= 0) { throw new ZepException("Limit can't be negative: " + limit); } List<Integer> severityIds = getSeverityIds(maxSeverity, inclusiveSeverity); if (severityIds.isEmpty()) { logger.debug("Not aging events - min severity specified"); return 0; } long now = System.currentTimeMillis(); long ageTs = now - agingIntervalMs; Map<String, Object> fields = new HashMap<String, Object>(); fields.put(COLUMN_STATUS_ID, EventStatus.STATUS_AGED.getNumber()); fields.put(COLUMN_CLOSED_STATUS, ZepConstants.CLOSED_STATUSES.contains(EventStatus.STATUS_AGED)); fields.put(COLUMN_STATUS_CHANGE, timestampConverter.toDatabaseType(now)); fields.put(COLUMN_UPDATE_TIME, timestampConverter.toDatabaseType(now)); fields.put(COLUMN_LAST_SEEN, timestampConverter.toDatabaseType(ageTs)); fields.put("_severity_ids", severityIds); fields.put("_limit", limit); final String updateSql; if (databaseCompatibility.getDatabaseType() == DatabaseType.MYSQL) { String indexSql = "SELECT uuid FROM event_summary " + " WHERE last_seen < :last_seen AND" + " severity_id IN (:_severity_ids) AND" + " closed_status = FALSE LIMIT :_limit"; this.indexResults(indexSql, fields); // Use UPDATE ... LIMIT updateSql = "UPDATE event_summary SET" + " status_id=:status_id,status_change=:status_change,update_time=:update_time" + ",closed_status=:closed_status" + " WHERE last_seen < :last_seen AND severity_id IN (:_severity_ids)" + " AND closed_status = FALSE LIMIT :_limit"; } else if (databaseCompatibility.getDatabaseType() == DatabaseType.POSTGRESQL) { String indexSql = "SELECT uuid FROM event_summary " + " WHERE uuid IN (SELECT uuid FROM event_summary WHERE" + " last_seen < :last_seen AND severity_id IN (:_severity_ids)" + " AND closed_status = FALSE LIMIT :_limit)"; this.indexResults(indexSql, fields); // Use UPDATE ... WHERE pk IN (SELECT ... LIMIT) updateSql = "UPDATE event_summary SET" + " status_id=:status_id,status_change=:status_change,update_time=:update_time" + ",closed_status=:closed_status" + " WHERE uuid IN (SELECT uuid FROM event_summary WHERE" + " last_seen < :last_seen AND severity_id IN (:_severity_ids)" + " AND closed_status = FALSE LIMIT :_limit)"; } else { throw new IllegalStateException("Unsupported database type: " + databaseCompatibility.getDatabaseType()); } final int numRows = this.template.update(updateSql, fields); if (numRows > 0) { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { counters.addToAgedEventCount(numRows); } }); } return numRows; } @Override @TransactionalRollbackAllExceptions @Timed public int addNote(String uuid, EventNote note) throws ZepException { final long updateTime = System.currentTimeMillis(); this.indexSignal(uuid); return this.eventDaoHelper.addNote(TABLE_EVENT_SUMMARY, uuid, note, template); } @Override @TransactionalRollbackAllExceptions @Timed public int updateDetails(String uuid, EventDetailSet details) throws ZepException { final long updateTime = System.currentTimeMillis(); this.indexSignal(uuid); return this.eventDaoHelper.updateDetails(TABLE_EVENT_SUMMARY, uuid, details.getDetailsList(), template); } private static class EventSummaryUpdateFields { private String currentUserUuid; private String currentUserName; private String clearedByEventUuid; public static final EventSummaryUpdateFields EMPTY_FIELDS = new EventSummaryUpdateFields(); public Map<String, Object> toMap(TypeConverter<String> uuidConverter) { Map<String, Object> m = new HashMap<String, Object>(); Object currentUuid = null; if (this.currentUserUuid != null) { currentUuid = uuidConverter.toDatabaseType(this.currentUserUuid); } m.put(COLUMN_CURRENT_USER_UUID, currentUuid); m.put(COLUMN_CURRENT_USER_NAME, currentUserName); Object clearedUuid = null; if (this.clearedByEventUuid != null) { clearedUuid = uuidConverter.toDatabaseType(this.clearedByEventUuid); } m.put(COLUMN_CLEARED_BY_EVENT_UUID, clearedUuid); return m; } public String getCurrentUserUuid() { return currentUserUuid; } public void setCurrentUserUuid(String currentUserUuid) { this.currentUserUuid = currentUserUuid; } public String getCurrentUserName() { return currentUserName; } public void setCurrentUserName(String currentUserName) { if (currentUserName == null) { this.currentUserName = null; } else { this.currentUserName = DaoUtils.truncateStringToUtf8(currentUserName, MAX_CURRENT_USER_NAME); } } public String getClearedByEventUuid() { return clearedByEventUuid; } public void setClearedByEventUuid(String clearedByEventUuid) { this.clearedByEventUuid = clearedByEventUuid; } } private int update(final List<String> uuids, final EventStatus status, final EventSummaryUpdateFields updateFields, final Collection<EventStatus> currentStatuses) throws ZepException { if (uuids.isEmpty()) { return 0; } TypeConverter<Long> timestampConverter = databaseCompatibility.getTimestampConverter(); final long now = System.currentTimeMillis(); final Map<String, Object> fields = updateFields.toMap(uuidConverter); fields.put(COLUMN_STATUS_ID, status.getNumber()); fields.put(COLUMN_STATUS_CHANGE, timestampConverter.toDatabaseType(now)); fields.put(COLUMN_CLOSED_STATUS, ZepConstants.CLOSED_STATUSES.contains(status)); fields.put(COLUMN_UPDATE_TIME, timestampConverter.toDatabaseType(now)); fields.put("_uuids", TypeConverterUtils.batchToDatabaseType(uuidConverter, uuids)); // If we aren't acknowledging events, we need to clear out the current user name / UUID values if (status != EventStatus.STATUS_ACKNOWLEDGED) { fields.put(COLUMN_CURRENT_USER_NAME, null); fields.put(COLUMN_CURRENT_USER_UUID, null); } StringBuilder sb = new StringBuilder("SELECT uuid,fingerprint,audit_json FROM event_summary"); StringBuilder sbw = new StringBuilder(" WHERE uuid IN (:_uuids)"); /* * This is required to support well-defined transitions between states. We only allow * updates to move events between states that make sense. */ if (!currentStatuses.isEmpty()) { final List<Integer> currentStatusIds = new ArrayList<Integer>(currentStatuses.size()); for (EventStatus currentStatus : currentStatuses) { currentStatusIds.add(currentStatus.getNumber()); } fields.put("_current_status_ids", currentStatusIds); sbw.append(" AND status_id IN (:_current_status_ids)"); } /* * Disallow acknowledging an event again as the same user name / user uuid. If the event is not * already acknowledged, we will allow it to be acknowledged (assuming state filter above doesn't * exclude it). Otherwise, we will only acknowledge it again if *either* the user name or user * uuid has changed. If neither of these fields have changed, it is a NO-OP. */ if (status == EventStatus.STATUS_ACKNOWLEDGED) { fields.put("_status_acknowledged", EventStatus.STATUS_ACKNOWLEDGED.getNumber()); sbw.append(" AND (status_id != :_status_acknowledged OR "); if (updateFields.getCurrentUserName() == null) { sbw.append("current_user_name IS NOT NULL"); } else { sbw.append("(current_user_name IS NULL OR current_user_name != :current_user_name)"); } sbw.append(" OR "); if (updateFields.getCurrentUserUuid() == null) { sbw.append("current_user_uuid IS NOT NULL"); } else { sbw.append("(current_user_uuid IS NULL OR current_user_uuid != :current_user_uuid)"); } sbw.append(")"); } String selectSql = sb.toString() + sbw.toString() + " FOR UPDATE"; /* * If this is a significant status change, also add an audit note */ final String newAuditJson; if (AUDIT_LOG_STATUSES.contains(status)) { EventAuditLog.Builder builder = EventAuditLog.newBuilder(); builder.setTimestamp(now); builder.setNewStatus(status); if (updateFields.getCurrentUserUuid() != null) { builder.setUserUuid(updateFields.getCurrentUserUuid()); } if (updateFields.getCurrentUserName() != null) { builder.setUserName(updateFields.getCurrentUserName()); } try { newAuditJson = JsonFormat.writeAsString(builder.build()); } catch (IOException e) { throw new ZepException(e); } } else { newAuditJson = null; } List<Map<String, Object>> result = this.template.query(selectSql, new RowMapper<Map<String, Object>>() { @Override public Map<String, Object> mapRow(ResultSet rs, int rowNum) throws SQLException { final String fingerprint = rs.getString(COLUMN_FINGERPRINT); final String currentAuditJson = rs.getString(COLUMN_AUDIT_JSON); Map<String, Object> updateFields = new HashMap<String, Object>(fields); final String newFingerprint; // When closing an event, give it a unique fingerprint hash if (ZepConstants.CLOSED_STATUSES.contains(status)) { updateFields.put(COLUMN_CLOSED_STATUS, Boolean.TRUE); newFingerprint = EventDaoUtils.join('|', fingerprint, Long.toString(now)); } // When re-opening an event, give it the true fingerprint_hash. This is required to correctly // de-duplicate events. else { updateFields.put(COLUMN_CLOSED_STATUS, Boolean.FALSE); newFingerprint = fingerprint; } final StringBuilder auditJson = new StringBuilder(); if (newAuditJson != null) { auditJson.append(newAuditJson); } if (currentAuditJson != null) { if (auditJson.length() > 0) { auditJson.append(",\n"); } auditJson.append(currentAuditJson); } String updatedAuditJson = (auditJson.length() > 0) ? auditJson.toString() : null; updateFields.put(COLUMN_FINGERPRINT_HASH, DaoUtils.sha1(newFingerprint)); updateFields.put(COLUMN_AUDIT_JSON, updatedAuditJson); updateFields.put(COLUMN_UUID, rs.getObject(COLUMN_UUID)); String uuid = uuidConverter.fromDatabaseType(rs, COLUMN_UUID); indexSignal(uuid); return updateFields; } }, fields); final String updateSql = "UPDATE event_summary SET status_id=:status_id,status_change=:status_change," + "closed_status=:closed_status,update_time=:update_time," + (status != EventStatus.STATUS_CLOSED && status != EventStatus.STATUS_CLEARED ? "current_user_uuid=:current_user_uuid,current_user_name=:current_user_name," : "") + "cleared_by_event_uuid=:cleared_by_event_uuid,fingerprint_hash=:fingerprint_hash," + "audit_json=:audit_json WHERE uuid=:uuid"; int numRows = 0; for (final Map<String, Object> update : result) { try { numRows += this.nestedTransactionService.executeInNestedTransaction( new NestedTransactionCallback<Integer>() { @Override public Integer doInNestedTransaction(NestedTransactionContext context) throws DataAccessException { return template.update(updateSql, update); } }); } catch (DuplicateKeyException e) { /* * Ignore duplicate key errors on update. This will occur if there is an active * event with the same fingerprint. */ } } return numRows; } @Override @TransactionalRollbackAllExceptions @Timed public int acknowledge(List<String> uuids, String userUuid, String userName) throws ZepException { /* NEW | ACKNOWLEDGED | SUPPRESSED -> ACKNOWLEDGED */ Set<EventStatus> currentStatuses = ZepConstants.OPEN_STATUSES; EventSummaryUpdateFields userfields = new EventSummaryUpdateFields(); userfields.setCurrentUserName(userName); userfields.setCurrentUserUuid(userUuid); return update(uuids, EventStatus.STATUS_ACKNOWLEDGED, userfields, currentStatuses); } private Map<String, Object> createSharedFields(long duration, TimeUnit unit) { TypeConverter<Long> timestampConverter = databaseCompatibility.getTimestampConverter(); long delta = System.currentTimeMillis() - unit.toMillis(duration); Object lastSeen = timestampConverter.toDatabaseType(delta); Map<String, Object> fields = new HashMap<String, Object>(); fields.put("_last_seen", lastSeen); return fields; } @Override @Timed public long getArchiveEligibleEventCount(long duration, TimeUnit unit) { String sql = "SELECT COUNT(*) FROM event_summary WHERE closed_status = TRUE AND last_seen < :_last_seen"; Map<String, Object> fields = createSharedFields(duration, unit); return template.queryForInt(sql, fields); } @Override @TransactionalRollbackAllExceptions @Timed public int archive(long duration, TimeUnit unit, int limit) throws ZepException { Map<String, Object> fields = createSharedFields(duration, unit); fields.put("_limit", limit); final String sql = "SELECT uuid FROM event_summary WHERE closed_status = TRUE AND " + "last_seen < :_last_seen LIMIT :_limit FOR UPDATE"; final List<String> uuids = this.template.query(sql, new RowMapper<String>() { @Override public String mapRow(ResultSet rs, int rowNum) throws SQLException { return uuidConverter.fromDatabaseType(rs, COLUMN_UUID); } }, fields); return archive(uuids); } @Override @TransactionalRollbackAllExceptions @Timed public int close(List<String> uuids, String userUuid, String userName) throws ZepException { /* NEW | ACKNOWLEDGED | SUPPRESSED -> CLOSED */ List<EventStatus> currentStatuses = Arrays.asList(EventStatus.STATUS_NEW, EventStatus.STATUS_ACKNOWLEDGED, EventStatus.STATUS_SUPPRESSED); EventSummaryUpdateFields userfields = new EventSummaryUpdateFields(); userfields.setCurrentUserName(userName); userfields.setCurrentUserUuid(userUuid); return update(uuids, EventStatus.STATUS_CLOSED, userfields, currentStatuses); } @Override @TransactionalRollbackAllExceptions @Timed public int reopen(List<String> uuids, String userUuid, String userName) throws ZepException { /* CLOSED | CLEARED | AGED | ACKNOWLEDGED | SUPPRESSED -> NEW */ List<EventStatus> currentStatuses = Arrays.asList(EventStatus.STATUS_CLOSED, EventStatus.STATUS_CLEARED, EventStatus.STATUS_AGED, EventStatus.STATUS_ACKNOWLEDGED, EventStatus.STATUS_SUPPRESSED); EventSummaryUpdateFields userfields = new EventSummaryUpdateFields(); userfields.setCurrentUserName(userName); userfields.setCurrentUserUuid(userUuid); return update(uuids, EventStatus.STATUS_NEW, userfields, currentStatuses); } @Override @TransactionalRollbackAllExceptions @Timed public int suppress(List<String> uuids) throws ZepException { /* NEW -> SUPPRESSED */ List<EventStatus> currentStatuses = Arrays.asList(EventStatus.STATUS_NEW); return update(uuids, EventStatus.STATUS_SUPPRESSED, EventSummaryUpdateFields.EMPTY_FIELDS, currentStatuses); } @Override @TransactionalRollbackAllExceptions @Timed public int archive(List<String> uuids) throws ZepException { if (uuids.isEmpty()) { return 0; } if (this.archiveColumnNames == null) { try { this.archiveColumnNames = DaoUtils.getColumnNames(this.dataSource, TABLE_EVENT_ARCHIVE); } catch (MetaDataAccessException e) { throw new ZepException(e.getLocalizedMessage(), e); } } TypeConverter<Long> timestampConverter = databaseCompatibility.getTimestampConverter(); Map<String, Object> fields = new HashMap<String, Object>(); fields.put(COLUMN_UPDATE_TIME, timestampConverter.toDatabaseType(System.currentTimeMillis())); fields.put("_uuids", TypeConverterUtils.batchToDatabaseType(uuidConverter, uuids)); StringBuilder selectColumns = new StringBuilder(); for (Iterator<String> it = this.archiveColumnNames.iterator(); it.hasNext(); ) { String columnName = it.next(); if (fields.containsKey(columnName)) { selectColumns.append(':').append(columnName); } else { selectColumns.append(columnName); } if (it.hasNext()) { selectColumns.append(','); } } final long updateTime = System.currentTimeMillis(); /* signal event_summary table rows to get indexed */ this.indexSignal(uuids); String insertSql = String.format("INSERT INTO event_archive (%s) SELECT %s FROM event_summary" + " WHERE uuid IN (:_uuids) AND closed_status = TRUE ON DUPLICATE KEY UPDATE summary=event_summary.summary", StringUtils.collectionToCommaDelimitedString(this.archiveColumnNames), selectColumns); this.template.update(insertSql, fields); final int updated = this.template.update("DELETE FROM event_summary WHERE uuid IN (:_uuids) AND closed_status = TRUE", fields); TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { counters.addToArchivedEventCount(updated); } }); return updated; } @Override @TransactionalRollbackAllExceptions @Timed public void importEvent(EventSummary eventSummary) throws ZepException { final long updateTime = System.currentTimeMillis(); final EventSummary.Builder summaryBuilder = EventSummary.newBuilder(eventSummary); final Event.Builder eventBuilder = summaryBuilder.getOccurrenceBuilder(0); summaryBuilder.setUpdateTime(updateTime); EventDaoHelper.addMigrateUpdateTimeDetail(eventBuilder, updateTime); final EventSummary summary = summaryBuilder.build(); final Map<String, Object> fields = this.eventDaoHelper.createImportedSummaryFields(summary); /* * Closed events have a unique fingerprint_hash in summary to allow multiple rows * but only allow one active event (where the de-duplication occurs). */ if (ZepConstants.CLOSED_STATUSES.contains(eventSummary.getStatus())) { String uniqueFingerprint = (String) fields.get(COLUMN_FINGERPRINT) + '|' + updateTime; fields.put(COLUMN_FINGERPRINT_HASH, DaoUtils.sha1(uniqueFingerprint)); fields.put(COLUMN_CLOSED_STATUS, Boolean.TRUE); } else { fields.put(COLUMN_FINGERPRINT_HASH, DaoUtils.sha1((String) fields.get(COLUMN_FINGERPRINT))); fields.put(COLUMN_CLOSED_STATUS, Boolean.FALSE); } if (eventSummary.getOccurrence(0).getSeverity() != EventSeverity.SEVERITY_CLEAR) { fields.put(COLUMN_CLEAR_FINGERPRINT_HASH, EventDaoUtils.createClearHash(eventSummary.getOccurrence(0))); } this.insert.execute(fields); } private void indexResults(final String sql, final Map<String, ?> fields) throws ZepException { List<String> ids = this.template.query(sql, new RowMapper<String>() { @Override public String mapRow(ResultSet rs, int rowNum) throws SQLException { return uuidConverter.fromDatabaseType(rs, COLUMN_UUID); } }, fields); this.indexSignal(ids); } private void indexSignal(final String eventUuid) { this.indexSignal(Collections.singletonList(eventUuid)); } private void indexSignal(final List<String> eventUuids) { if (!txSynchronizedQueue) { doIndexSignal(eventUuids); return; } IndexQueueSynchronizer idxSync = null; for (TransactionSynchronization sync : TransactionSynchronizationManager.getSynchronizations()) { if (sync instanceof IndexQueueSynchronizer) { idxSync = (IndexQueueSynchronizer) sync; break; } } if (idxSync == null) { idxSync = new IndexQueueSynchronizer(); TransactionSynchronizationManager.registerSynchronization(idxSync); } idxSync.uuids.addAll(eventUuids); } private void doIndexSignal(final Collection<String> eventUuids) { if (eventUuids.isEmpty()) { return; } Long updateTime = System.currentTimeMillis(); List<EventIndexBackendTask> tasks = Lists.newArrayListWithCapacity(eventUuids.size()); for (String uuid : eventUuids) { tasks.add(EventIndexBackendTask.Index(uuid, updateTime)); } eventIndexQueue.addAll(tasks); } private class IndexQueueSynchronizer extends TransactionSynchronizationAdapter { TreeSet<String> uuids = new TreeSet<>(); @Override public void afterCommit() { doIndexSignal(uuids); } } }