/***************************************************************************** * * Copyright (C) Zenoss, Inc. 2010-2011, 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.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.protobuf.Message; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.jdbc.core.simple.SimpleJdbcOperations; 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.EventDetail; import org.zenoss.protobufs.zep.Zep.EventDetail.EventDetailMergeBehavior; import org.zenoss.protobufs.zep.Zep.EventNote; import org.zenoss.protobufs.zep.Zep.EventSeverity; import org.zenoss.protobufs.zep.Zep.EventSummary; import org.zenoss.protobufs.zep.Zep.EventTag; import org.zenoss.protobufs.zep.Zep.EventTag.Builder; import org.zenoss.protobufs.zep.Zep.SyslogPriority; import org.zenoss.utils.dao.Partition; import org.zenoss.utils.dao.RangePartitioner; import org.zenoss.zep.UUIDGenerator; import org.zenoss.zep.ZepConfigService; import org.zenoss.zep.ZepConstants; import org.zenoss.zep.ZepException; import org.zenoss.zep.ZepUtils; import org.zenoss.zep.annotations.TransactionalReadOnly; import org.zenoss.zep.dao.DaoCache; import org.zenoss.zep.dao.EventBatch; import org.zenoss.zep.dao.EventBatchParams; import org.zenoss.zep.dao.impl.compat.DatabaseCompatibility; import org.zenoss.zep.dao.impl.compat.TypeConverter; import java.io.IOException; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import static org.zenoss.zep.dao.impl.EventConstants.*; public class EventDaoHelper { private DaoCache daoCache; private UUIDGenerator uuidGenerator; private DatabaseCompatibility databaseCompatibility; private TypeConverter<String> uuidConverter; private ZepConfigService zepConfigService; @SuppressWarnings("unused") private static final Logger logger = LoggerFactory.getLogger(EventDaoHelper.class); public EventDaoHelper() { } public void setZepConfigService(ZepConfigService zepConfigService) throws ZepException { this.zepConfigService = zepConfigService; } public void setDaoCache(DaoCache daoCache) { this.daoCache = daoCache; } public void setUuidGenerator(UUIDGenerator uuidGenerator) { this.uuidGenerator = uuidGenerator; } public void setDatabaseCompatibility(DatabaseCompatibility databaseCompatibility) { this.databaseCompatibility = databaseCompatibility; this.uuidConverter = databaseCompatibility.getUUIDConverter(); } private boolean isValidDetailsSize(String str, long eventMaxSizeBytes) throws ZepException { return str.length() * 2 < eventMaxSizeBytes; } private List<EventDetail> removeNonZenossDetails(List<EventDetail> details) { List<EventDetail> cleanDetails = new ArrayList<EventDetail>(); for (EventDetail detail : details) { if (detail.getName().startsWith("zenoss.")) { cleanDetails.add(detail); } } return cleanDetails; } public Map<String, Object> createOccurrenceFields(Event event) throws ZepException { Map<String, Object> fields = new HashMap<String, Object>(); String fingerprint = DaoUtils.truncateStringToUtf8(event.getFingerprint(), MAX_FINGERPRINT); fields.put(COLUMN_FINGERPRINT, fingerprint); Integer eventGroupId = null; if (event.hasEventGroup()) { String eventGroup = DaoUtils.truncateStringToUtf8(event.getEventGroup(), MAX_EVENT_GROUP); eventGroupId = daoCache.getEventGroupId(eventGroup); } fields.put(COLUMN_EVENT_GROUP_ID, eventGroupId); String eventClass = DaoUtils.truncateStringToUtf8(event.getEventClass(), MAX_EVENT_CLASS); fields.put(COLUMN_EVENT_CLASS_ID, daoCache.getEventClassId(eventClass)); Integer eventClassKeyId = null; if (event.hasEventClassKey()) { String eventClassKey = DaoUtils.truncateStringToUtf8(event.getEventClassKey(), MAX_EVENT_CLASS_KEY); eventClassKeyId = daoCache.getEventClassKeyId(eventClassKey); } fields.put(COLUMN_EVENT_CLASS_KEY_ID, eventClassKeyId); Integer eventKeyId = null; if (event.hasEventKey()) { String eventKey = DaoUtils.truncateStringToUtf8(event.getEventKey(), MAX_EVENT_KEY); eventKeyId = daoCache.getEventKeyId(eventKey); } fields.put(COLUMN_EVENT_KEY_ID, eventKeyId); Object eventClassMappingUuid = null; if (!event.getEventClassMappingUuid().isEmpty()) { eventClassMappingUuid = uuidConverter.toDatabaseType(event.getEventClassMappingUuid()); } fields.put(COLUMN_EVENT_CLASS_MAPPING_UUID, eventClassMappingUuid); fields.put(COLUMN_SEVERITY_ID, event.getSeverity().getNumber()); if (event.hasActor()) { populateEventActorFields(event.getActor(), fields); } Integer monitorId = null; if (event.hasMonitor()) { monitorId = daoCache.getMonitorId(DaoUtils.truncateStringToUtf8(event.getMonitor(), MAX_MONITOR)); } fields.put(COLUMN_MONITOR_ID, monitorId); Integer agentId = null; if (event.hasAgent()) { agentId = daoCache.getAgentId(DaoUtils.truncateStringToUtf8(event.getAgent(), MAX_AGENT)); } fields.put(COLUMN_AGENT_ID, agentId); Integer syslogFacility = null; if (event.hasSyslogFacility()) { syslogFacility = event.getSyslogFacility(); } fields.put(COLUMN_SYSLOG_FACILITY, syslogFacility); Integer syslogPriority = null; if (event.hasSyslogPriority()) { syslogPriority = event.getSyslogPriority().getNumber(); } fields.put(COLUMN_SYSLOG_PRIORITY, syslogPriority); Integer ntEventCode = null; if (event.hasNtEventCode()) { ntEventCode = event.getNtEventCode(); } fields.put(COLUMN_NT_EVENT_CODE, ntEventCode); fields.put(COLUMN_SUMMARY, DaoUtils.truncateStringToUtf8(event.getSummary(), MAX_SUMMARY)); fields.put(COLUMN_MESSAGE, DaoUtils.truncateStringToUtf8(event.getMessage(), MAX_MESSAGE)); String detailsJson = null; if (event.getDetailsCount() > 0) { try { detailsJson = JsonFormat.writeAllDelimitedAsString(mergeDuplicateDetails(event.getDetailsList())); // if the detailsJson string is too big, filter out non-Zenoss // details and try again. long eventMaxSizeBytes = zepConfigService.getConfig().getEventMaxSizeBytes(); if (!isValidDetailsSize(detailsJson, eventMaxSizeBytes)) { detailsJson = JsonFormat.writeAllDelimitedAsString( mergeDuplicateDetails(removeNonZenossDetails(event.getDetailsList()))); if (!isValidDetailsSize(detailsJson, eventMaxSizeBytes)) { // TODO: What to do when we can't reduce the size enough? logger.warn("Could not reduce event size below event_max_size_bytes setting: " + zepConfigService.getConfig().getEventMaxSizeBytes() + " Event: " + event); } } } catch (IOException e) { throw new ZepException(e.getLocalizedMessage(), e); } } fields.put(COLUMN_DETAILS_JSON, detailsJson); String tagsJson = null; if (event.getTagsCount() > 0) { List<EventTag> tags = buildTags(event); try { tagsJson = JsonFormat.writeAllDelimitedAsString(tags); } catch (IOException e) { throw new ZepException(e.getLocalizedMessage(), e); } } fields.put(COLUMN_TAGS_JSON, tagsJson); return fields; } /** * Removes duplicate tags from the event. * * @param event * Original event. * @return New event with duplicate tags removed. */ public static List<EventTag> buildTags(Event event) { final Map<String,EventTag.Builder> tagTypesMap = new TreeMap<String,Builder>(); final Set<String> uuids = new HashSet<String>(); for (EventTag tag : event.getTagsList()) { EventTag.Builder tagBuilder = tagTypesMap.get(tag.getType()); if (tagBuilder == null) { tagBuilder = EventTag.newBuilder(); tagBuilder.setType(tag.getType()); tagTypesMap.put(tag.getType(), tagBuilder); } for (String tagUuid : tag.getUuidList()) { if (uuids.add(tagUuid)) { tagBuilder.addUuid(tagUuid); } } } final List<EventTag> tags = new ArrayList<EventTag>(tagTypesMap.size()); for (EventTag.Builder tagBuilder : tagTypesMap.values()) { if (tagBuilder.getUuidCount() > 0) { tags.add(tagBuilder.build()); } } return tags; } /** * Collapses duplicates in a list of event details. * * @param details Event details (may contain duplicate names). * @return A collection of event details where duplicates are removed and values are appended. */ private static Collection<EventDetail> mergeDuplicateDetails(List<EventDetail> details) { Map<String, EventDetail> names = new LinkedHashMap<String, EventDetail>(details.size()); for (EventDetail detail : details) { EventDetail existing = names.get(detail.getName()); if (existing == null) { names.put(detail.getName(), detail); } else { // Append detail values to existing EventDetail merged = EventDetail.newBuilder(existing).addAllValue(detail.getValueList()).build(); names.put(detail.getName(), merged); } } return names.values(); } /** * Merges the old and new event detail lists. Uses the EventDetailMergeBehavior setting * to determine how details with the same name in both lists should be handled. * * @param oldDetails Old event details. * @param newDetails New event details. * @return A JSON string of the details aftering merging. * @throws org.zenoss.zep.ZepException X */ public String mergeDetailsToJson(List<EventDetail> oldDetails, List<EventDetail> newDetails) throws ZepException { Map<String, EventDetail> detailsMap = mergeDetails(oldDetails, newDetails); try { String results = JsonFormat.writeAllDelimitedAsString(detailsMap.values()); long eventMaxSizeBytes = zepConfigService.getConfig().getEventMaxSizeBytes(); if (!isValidDetailsSize(results, eventMaxSizeBytes)) { final String newDetailsJson = JsonFormat.writeAllDelimitedAsString(newDetails); if (isValidDetailsSize(newDetailsJson, eventMaxSizeBytes)) { // new details are a valid size, truncate the old and use the new results = newDetailsJson; logger.warn("Truncating old details because details are not a valid size: " + oldDetails); } else { // If the entire set of new details is not small enough, // truncate all non-zenoss details. final String originalResults = results; final List<EventDetail> newZenossDetails = removeNonZenossDetails(newDetails); results = JsonFormat.writeAllDelimitedAsString(newZenossDetails); logger.warn("Truncating old details because details are not a valid size. " + "New non-Zenoss details have also been truncated due to size. " + "ORIGINAL DATA: " + originalResults); } } return results; } catch (IOException e) { throw new ZepException(e.getLocalizedMessage(), e); } } public static Map<String, EventDetail> mergeDetails(List<EventDetail> oldDetails, List<EventDetail> newDetails) { Map<String, EventDetail> detailsMap = new LinkedHashMap<String, EventDetail>(oldDetails.size() + newDetails.size()); for (EventDetail detail : oldDetails) { detailsMap.put(detail.getName(), detail); } for (EventDetail newDetail : newDetails) { final EventDetailMergeBehavior mergeBehavior = newDetail.getMergeBehavior(); if (mergeBehavior == EventDetailMergeBehavior.REPLACE) { // If a new detail specifies an empty value, it is removed if (newDetail.getValueCount() == 0) { detailsMap.remove(newDetail.getName()); } else { detailsMap.put(newDetail.getName(), newDetail); } } else { final EventDetail existing = detailsMap.get(newDetail.getName()); if (existing == null) { detailsMap.put(newDetail.getName(), newDetail); } else if (mergeBehavior == EventDetailMergeBehavior.APPEND) { final EventDetail.Builder merged = EventDetail.newBuilder(existing); merged.addAllValue(newDetail.getValueList()); detailsMap.put(newDetail.getName(), merged.build()); } else if (mergeBehavior == EventDetailMergeBehavior.UNIQUE) { final EventDetail.Builder merged = EventDetail.newBuilder(existing); final Set<String> newValues = new LinkedHashSet<String>(newDetail.getValueList()); newValues.removeAll(existing.getValueList()); merged.addAllValue(newValues); detailsMap.put(newDetail.getName(), merged.build()); } else { logger.warn("Unsupported merge behavior: {}", mergeBehavior); } } } return detailsMap; } private void populateEventActorFields(EventActor actor, Map<String, Object> fields) { if (!actor.getElementUuid().isEmpty()) { fields.put(COLUMN_ELEMENT_UUID, uuidConverter.toDatabaseType(actor.getElementUuid())); } if (actor.hasElementTypeId()) { fields.put(COLUMN_ELEMENT_TYPE_ID, actor.getElementTypeId().getNumber()); } if (actor.hasElementIdentifier()) { final String elementId = DaoUtils.truncateStringToUtf8(actor.getElementIdentifier(), MAX_ELEMENT_IDENTIFIER); fields.put(COLUMN_ELEMENT_IDENTIFIER, elementId); } if (actor.hasElementTitle()) { final String elementTitle = DaoUtils.truncateStringToUtf8(actor.getElementTitle(), MAX_ELEMENT_TITLE); fields.put(COLUMN_ELEMENT_TITLE, elementTitle); } if (!actor.getElementSubUuid().isEmpty()) { fields.put(COLUMN_ELEMENT_SUB_UUID, uuidConverter.toDatabaseType(actor.getElementSubUuid())); } if (actor.hasElementSubTypeId()) { fields.put(COLUMN_ELEMENT_SUB_TYPE_ID, actor.getElementSubTypeId().getNumber()); } if (actor.hasElementSubIdentifier()) { final String elementSubId = DaoUtils.truncateStringToUtf8(actor.getElementSubIdentifier(), MAX_ELEMENT_SUB_IDENTIFIER); fields.put(COLUMN_ELEMENT_SUB_IDENTIFIER, elementSubId); } if (actor.hasElementSubTitle()) { final String elementSubTitle = DaoUtils.truncateStringToUtf8(actor.getElementSubTitle(), MAX_ELEMENT_SUB_TITLE); fields.put(COLUMN_ELEMENT_SUB_TITLE, elementSubTitle); } } public Event eventMapper(ResultSet rs, final boolean isArchive) throws SQLException { TypeConverter<Long> timestampConverter = databaseCompatibility.getTimestampConverter(); Event.Builder eventBuilder = Event.newBuilder(); eventBuilder.setCreatedTime(timestampConverter.fromDatabaseType(rs, COLUMN_LAST_SEEN)); eventBuilder.setFingerprint(rs.getString(COLUMN_FINGERPRINT)); int eventGroupId = rs.getInt(COLUMN_EVENT_GROUP_ID); if (!rs.wasNull()) { eventBuilder.setEventGroup(daoCache.getEventGroupFromId(eventGroupId)); } int eventClassId = rs.getInt(COLUMN_EVENT_CLASS_ID); if (!rs.wasNull()) { eventBuilder.setEventClass(daoCache.getEventClassFromId(eventClassId)); } int eventClassKeyId = rs.getInt(COLUMN_EVENT_CLASS_KEY_ID); if (!rs.wasNull()) { eventBuilder.setEventClassKey(daoCache.getEventClassKeyFromId(eventClassKeyId)); } int eventKeyId = rs.getInt(COLUMN_EVENT_KEY_ID); if (!rs.wasNull()) { eventBuilder.setEventKey(daoCache.getEventKeyFromId(eventKeyId)); } String eventClassMappingUuid = uuidConverter.fromDatabaseType(rs, COLUMN_EVENT_CLASS_MAPPING_UUID); if (eventClassMappingUuid != null) { eventBuilder.setEventClassMappingUuid(eventClassMappingUuid); } eventBuilder.setSeverity(EventSeverity.valueOf(rs.getInt(COLUMN_SEVERITY_ID))); eventBuilder.setActor(deserializeEventActor(rs)); int monitorId = rs.getInt(COLUMN_MONITOR_ID); if (!rs.wasNull()) { eventBuilder.setMonitor(daoCache.getMonitorFromId(monitorId)); } int agentId = rs.getInt(COLUMN_AGENT_ID); if (!rs.wasNull()) { eventBuilder.setAgent(daoCache.getAgentFromId(agentId)); } int syslogFacility = rs.getInt(COLUMN_SYSLOG_FACILITY); if (!rs.wasNull()) { eventBuilder.setSyslogFacility(syslogFacility); } int syslogPriority = rs.getInt(COLUMN_SYSLOG_PRIORITY); if (!rs.wasNull()) { eventBuilder.setSyslogPriority(SyslogPriority.valueOf(syslogPriority)); } int ntEventCode = rs.getInt(COLUMN_NT_EVENT_CODE); if (!rs.wasNull()) { eventBuilder.setNtEventCode(ntEventCode); } eventBuilder.setSummary(rs.getString(COLUMN_SUMMARY)); eventBuilder.setMessage(rs.getString(COLUMN_MESSAGE)); String detailsJson = rs.getString(COLUMN_DETAILS_JSON); if (detailsJson != null && !detailsJson.isEmpty()) { try { List<EventDetail> details = JsonFormat.mergeAllDelimitedFrom(detailsJson, EventDetail.getDefaultInstance()); eventBuilder.addAllDetails(details); } catch (IOException e) { throw new SQLException(e); } } if (isArchive == true) { EventDetail archiveDetail = EventDetail.newBuilder().setName("is_archive").addValue("true").build(); eventBuilder.addDetails(archiveDetail); } String tagsJson = rs.getString(COLUMN_TAGS_JSON); if (tagsJson != null && !tagsJson.isEmpty()) { try { List<EventTag> tags = JsonFormat.mergeAllDelimitedFrom(tagsJson, EventTag.getDefaultInstance()); eventBuilder.addAllTags(tags); } catch (IOException e) { throw new SQLException(e); } } return eventBuilder.build(); } private EventActor deserializeEventActor(ResultSet rs) throws SQLException { final EventActor.Builder actorBuilder = EventActor.newBuilder(); String elementUuid = uuidConverter.fromDatabaseType(rs, COLUMN_ELEMENT_UUID); if (elementUuid != null) { actorBuilder.setElementUuid(elementUuid); } final int elementTypeId = rs.getInt(COLUMN_ELEMENT_TYPE_ID); if (!rs.wasNull()) { actorBuilder.setElementTypeId(ModelElementType.valueOf(elementTypeId)); } final String elementIdentifier = rs.getString(COLUMN_ELEMENT_IDENTIFIER); if (elementIdentifier != null) { actorBuilder.setElementIdentifier(elementIdentifier); } final String elementTitle = rs.getString(COLUMN_ELEMENT_TITLE); if (elementTitle != null) { actorBuilder.setElementTitle(elementTitle); } // titleOrId else if (elementIdentifier != null) { actorBuilder.setElementTitle(elementIdentifier); } String subUuid = uuidConverter.fromDatabaseType(rs, COLUMN_ELEMENT_SUB_UUID); if (subUuid != null) { actorBuilder.setElementSubUuid(subUuid); } final int subTypeId = rs.getInt(COLUMN_ELEMENT_SUB_TYPE_ID); if (!rs.wasNull()) { actorBuilder.setElementSubTypeId(ModelElementType.valueOf(subTypeId)); } final String subIdentifier = rs.getString(COLUMN_ELEMENT_SUB_IDENTIFIER); if (subIdentifier != null) { actorBuilder.setElementSubIdentifier(subIdentifier); } final String subTitle = rs.getString(COLUMN_ELEMENT_SUB_TITLE); if (subTitle != null) { actorBuilder.setElementSubTitle(subTitle); } // titleOrId else if (subIdentifier != null) { actorBuilder.setElementSubTitle(subIdentifier); } return actorBuilder.build(); } public int addNote(String tableName, String uuid, EventNote note, SimpleJdbcOperations template) throws ZepException { TypeConverter<Long> timestampConverter = databaseCompatibility.getTimestampConverter(); EventNote.Builder builder = EventNote.newBuilder(note); if (builder.getUuid().isEmpty()) { builder.setUuid(this.uuidGenerator.generate().toString()); } builder.setCreatedTime(System.currentTimeMillis()); try { Map<String,Object> fields = new HashMap<String,Object>(); fields.put(COLUMN_UPDATE_TIME, timestampConverter.toDatabaseType(System.currentTimeMillis())); fields.put(COLUMN_UUID, uuidConverter.toDatabaseType(uuid)); // Get the current notes (if any) final String querySql = "SELECT notes_json FROM " + tableName + " WHERE uuid=:uuid FOR UPDATE"; final String currentNoteJson; try { currentNoteJson = template.queryForObject(querySql, String.class, fields); } catch (EmptyResultDataAccessException e) { // If the event doesn't exist, we return 0 as the number of affected rows return 0; } // Prepend the new note final StringBuilder newNoteJson = new StringBuilder(JsonFormat.writeAsString(builder.build())); if (currentNoteJson != null) { newNoteJson.append(",\n").append(currentNoteJson); } fields.put(COLUMN_NOTES_JSON, newNoteJson.toString()); try { final String updateSql = "UPDATE " + tableName + " SET update_time=:update_time,notes_json=:notes_json" + " WHERE uuid=:uuid"; return template.update(updateSql, fields); } catch (DataIntegrityViolationException e) { int sizeOfNotes = newNoteJson.length(); logger.warn(" Truncating the NotesJson field value to half to avoid DataIntegrityExceptions"); final StringBuilder updatedNoteJson; updatedNoteJson = newNoteJson.delete(0,newNoteJson.indexOf(",\n",sizeOfNotes/2)+1); fields.put(COLUMN_NOTES_JSON, updatedNoteJson.toString()); final String updateSql = "UPDATE " + tableName + " SET update_time=:update_time,notes_json=:notes_json" + " WHERE uuid=:uuid"; return template.update(updateSql, fields); } } catch (IOException e) { throw new ZepException(e); } } public int updateDetails(String tableName, String uuid, List<EventDetail> details, SimpleJdbcOperations template) throws ZepException { Map<String, Object> fields = new HashMap<String, Object>(); fields.put(COLUMN_UUID, uuidConverter.toDatabaseType(uuid)); final String selectSql = "SELECT details_json FROM " + tableName + " WHERE uuid = :uuid FOR UPDATE"; final List<EventDetail> existingDetailList; try { String currentDetailsJson = template.queryForObject(selectSql, String.class, fields); if (currentDetailsJson == null) { existingDetailList = Collections.emptyList(); } else { existingDetailList = JsonFormat.mergeAllDelimitedFrom(currentDetailsJson, EventDetail.getDefaultInstance()); } } catch (IncorrectResultSizeDataAccessException irsdae) { logger.debug("unexpected results size data access exception retrieving event summary", irsdae); return 0; } catch (IOException e) { throw new ZepException(e); } // update details with new values final String updatedDetailsJson = mergeDetailsToJson(existingDetailList, details); fields.put(COLUMN_DETAILS_JSON, updatedDetailsJson); TypeConverter<Long> timestampConverter = databaseCompatibility.getTimestampConverter(); fields.put(COLUMN_UPDATE_TIME, timestampConverter.toDatabaseType(System.currentTimeMillis())); String updateSql = "UPDATE " + tableName + " SET details_json=:details_json, " + "update_time=:update_time WHERE uuid = :uuid"; return template.update(updateSql, fields); } public static List<Integer> getSeverityIdsLessThan(EventSeverity severity) { final List<Integer> severityIds = new ArrayList<Integer>(ZepUtils.ORDERED_SEVERITIES.size() - 1); for (EventSeverity orderedSeverity : ZepUtils.ORDERED_SEVERITIES) { if (orderedSeverity == severity) { break; } severityIds.add(orderedSeverity.getNumber()); } return severityIds; } @TransactionalReadOnly private List<EventSummary> listBatch(SimpleJdbcOperations template, String tableName, String startingUuid, long maxUpdateTime, int limit, EventSummaryRowMapper esrm) throws ZepException { final String sql; final Map<String,Object> fields = new HashMap<String,Object>(); fields.put("_max_update_time", databaseCompatibility.getTimestampConverter().toDatabaseType(maxUpdateTime)); fields.put("_limit", limit); if (startingUuid == null) { sql = "SELECT * FROM " + tableName + " WHERE update_time <= :_max_update_time ORDER BY uuid LIMIT :_limit"; } else { fields.put("_starting_uuid", uuidConverter.toDatabaseType(startingUuid)); sql = "SELECT * FROM " + tableName + " WHERE uuid > :_starting_uuid AND update_time <= :_max_update_time " + "ORDER BY uuid LIMIT :_limit"; } return template.query(sql, esrm, fields); } @TransactionalReadOnly public EventBatch listBatch(SimpleJdbcOperations template, String tableName, RangePartitioner partitioner, EventBatchParams batchParams, long maxUpdateTime, int limit, EventSummaryRowMapper esrm) throws ZepException { if (partitioner == null) { List<EventSummary> events = listBatch(template, tableName, batchParams == null ? null : batchParams.nextUuid, maxUpdateTime, limit, esrm); if (events.isEmpty()) { return new EventBatch(events, Long.MIN_VALUE, null); } else { return new EventBatch(events, Long.MIN_VALUE, Iterables.getLast(events).getUuid()); } } else { final Object maxUpdateTimeObject = databaseCompatibility.getTimestampConverter().toDatabaseType(maxUpdateTime); List<EventSummary> events = new ArrayList<EventSummary>(limit); long nextLastSeen = (batchParams == null) ? Long.MAX_VALUE : batchParams.nextLastSeen; String nextUuid = (batchParams == null) ? null : batchParams.nextUuid; for (Partition p : Lists.reverse(partitioner.listPartitions())) { if (p.getRangeMinimum() != null) { long partitionMin = p.getRangeMinimum().getTime(); if (partitionMin > nextLastSeen) continue; else if (partitionMin < nextLastSeen) nextLastSeen = partitionMin; events.addAll(listBatchInPartition(template, tableName, p, nextUuid, maxUpdateTimeObject, limit - events.size(), esrm)); if (events.size() >= limit) { nextUuid = Iterables.getLast(events).getUuid(); break; } else { nextUuid = null; } } else { nextLastSeen = Long.MIN_VALUE; events.addAll(listBatchInPartition(template, tableName, p, nextUuid, maxUpdateTimeObject, limit - events.size(), esrm)); if (events.size() >= limit) { nextUuid = Iterables.getLast(events).getUuid(); break; } else { nextUuid = null; } } } return new EventBatch(events, nextLastSeen, nextUuid); } } @TransactionalReadOnly private List<EventSummary> listBatchInPartition(SimpleJdbcOperations template, String tableName, Partition partition, String nextUuid, Object maxUpdateTime, int limit, EventSummaryRowMapper esrm) throws ZepException { final Map<String,Object> fields = new HashMap<String,Object>(); final StringBuffer sql = new StringBuffer(); sql.append("SELECT * FROM "); sql.append(tableName); sql.append(" WHERE update_time <= :_max_update_time"); fields.put("_max_update_time", maxUpdateTime); if (partition.getRangeMinimum() != null) { sql.append(" AND last_seen >= :_range_min"); fields.put("_range_min", partition.getRangeMinimum().getTime()); } if (partition.getRangeLessThan() != null) { sql.append(" AND last_seen <= :_range_max"); fields.put("_range_max", partition.getRangeLessThan().getTime()); } if (nextUuid != null) { sql.append(" AND uuid > :_starting_uuid"); fields.put("_starting_uuid", uuidConverter.toDatabaseType(nextUuid)); } sql.append(" ORDER BY uuid LIMIT :_limit"); fields.put("_limit", limit); return template.query(sql.toString(), esrm, fields); } /** * Adds the {@link ZepConstants#DETAIL_MIGRATE_UPDATE_TIME} detail to the event occurrence. * * @param eventBuilder Event builder. * @param updateTime Update time to use as value for event detail. */ public static void addMigrateUpdateTimeDetail(Event.Builder eventBuilder, long updateTime) { // Add migrate_update_time detail final int detailsCount = eventBuilder.getDetailsCount(); for (int i = 0; i < detailsCount; i++) { EventDetail detail = eventBuilder.getDetails(i); // Clear out existing detail if it exists if (ZepConstants.DETAIL_MIGRATE_UPDATE_TIME.equals(detail.getName())) { eventBuilder.getDetailsBuilder(i).clearValue(); } } eventBuilder.addDetails(EventDetail.newBuilder().setName(ZepConstants.DETAIL_MIGRATE_UPDATE_TIME) .addValue(Long.toString(updateTime))); } private static String collectionToJsonDelimited(List<? extends Message> messages) throws ZepException { if (messages.isEmpty()) { return null; } final StringBuilder sb = new StringBuilder(); try { for (Iterator<? extends Message> it = messages.iterator(); it.hasNext(); ) { sb.append(JsonFormat.writeAsString(it.next())); if (it.hasNext()) { sb.append(",\n"); } } } catch (IOException e) { throw new ZepException(e.getLocalizedMessage(), e); } return sb.toString(); } /** * Creates a map of column names to values suitable for inserting into the event_summary and event_archive tables. * * @param summary Event summary to be created. * @return Map of field names to values. * @throws ZepException If the summary can't be serialized. */ public Map<String,Object> createImportedSummaryFields(EventSummary summary) throws ZepException { TypeConverter<Long> timestampConverter = databaseCompatibility.getTimestampConverter(); final Map<String, Object> fields = createOccurrenceFields(summary.getOccurrence(0)); fields.put(COLUMN_UUID, uuidConverter.toDatabaseType(summary.getUuid())); fields.put(COLUMN_STATUS_ID, summary.getStatus().getNumber()); 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.hasCurrentUserUuid()) { fields.put(COLUMN_CURRENT_USER_UUID, uuidConverter.toDatabaseType(summary.getCurrentUserUuid())); } if (summary.hasCurrentUserName()) { fields.put(COLUMN_CURRENT_USER_NAME, summary.getCurrentUserName()); } if (summary.hasClearedByEventUuid()) { fields.put(COLUMN_CLEARED_BY_EVENT_UUID, uuidConverter.toDatabaseType(summary.getClearedByEventUuid())); } fields.put(COLUMN_NOTES_JSON, EventDaoHelper.collectionToJsonDelimited(summary.getNotesList())); fields.put(COLUMN_AUDIT_JSON, EventDaoHelper.collectionToJsonDelimited(summary.getAuditLogList())); return fields; } }