org.zenoss.zep.dao.impl.EventDaoHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.zenoss.zep.dao.impl.EventDaoHelper.java

Source

/*****************************************************************************
 * 
 * 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.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());

            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;
    }

}