/* * Copyright 2008 Glencoe Software, Inc. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ package ome.services.eventlogs; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import ome.model.meta.EventLog; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Thread-safe java.util-like Container for storing {@link EventLog} instances * for later parsing. This container, however, will not add more than two * {@link EventLog logs} with the same (id, eventType, action) tuple. * * Further, the container can only either be in the adding state or the removing * state. A newly created {@link EventBacklog} is in the adding state. The * popping state is entered the first time that {@link #remove()} is called. And * the adding state will only be re-entered, once {@link #remove()} has returned * null and {@link #logs} is empty. * * All calls to {@link #add(EventLog)} while in the popping state will return * false. * * @author Josh Moore, josh at glencoesoftware.com * @since 3.0-Beta3.1 */ public class EventBacklog { final private static Logger logger = LoggerFactory.getLogger(EventBacklog.class); final Map<Long, Map<String, Set<String>>> contained = new HashMap<Long, Map<String, Set<String>>>(); final List<EventLog> logs = new ArrayList<EventLog>(); /** * Switch between the adding and the removing states. */ protected boolean adding = true; /** * Adds the given {@link EventLog} instance to the end of a queue for later * {@link #remove() popping} if no equivalent {@link EventLog} is present. * Equivalence is based on the entityType, entityId, and action fields. * Records tracking information to prevent the same {@link EventLog} from * being re-added before the last instance is removed. */ public synchronized boolean add(EventLog log) { if (!adding) { if (logger.isInfoEnabled()) { logger.info("Backlog locked:" + log.getEntityType() + ":Id_" + log.getEntityId()); } return false; } if (log == null || log.getEntityType() == null || log.getEntityId() == null || log.getAction() == null) { throw new IllegalArgumentException( "EventLog must contain entityType, entityId, and action"); } Map<String, Set<String>> class2action = contained .get(log.getEntityId()); if (class2action == null) { class2action = new HashMap<String, Set<String>>(); contained.put(log.getEntityId(), class2action); } Set<String> actions = class2action.get(log.getEntityType()); if (actions == null) { actions = new HashSet<String>(); class2action.put(log.getEntityType(), actions); } boolean contained = actions.contains(log.getAction()); if (contained) { if (logger.isInfoEnabled()) { logger.info("Already in backlog:" + log.getEntityType() + ":Id_" + log.getEntityId()); } return false; } else { actions.add(log.getAction()); logs.add(log); if (logger.isInfoEnabled()) { logger.info("Added to backlog:" + log.getEntityType() + ":Id_" + log.getEntityId()); } return true; } } /** * Removes and returns the next {@link EventLog} instance or null if none is * present. Also cleans up any tracking information for the given * {@link EventLog}. * * @return See above. */ public synchronized EventLog remove() { if (logs.size() == 0) { contained.clear(); return null; // EARLY EXIT } adding = false; EventLog log = logs.remove(0); // None of these can be null as long as the log is still contained Map<String, Set<String>> class2action = contained .get(log.getEntityId()); Set<String> actions = class2action.get(log.getEntityType()); assert actions.remove(log.getAction()); if (actions.size() == 0) { class2action.remove(log.getEntityType()); if (class2action.size() == 0) { contained.remove(log.getEntityId()); } } return log; } /** * Flips the {@link EventBacklog} to the "adding" state if it is empty, * otherwise to the "removing" state. This is necessary since the indexing * happens only at flush time in the * {@link ome.services.fulltext.FullTextIndexer}. This * restriction means in any one batch only either backlog or new event logs * will be processed. * * @see EventLogLoader#hasNext() */ public synchronized void flipState() { adding = (logs.size() == 0); } /** * Check the current state of the {@link EventBacklog}. If in the * "removingOnly" state, then any calls to {@link #add(EventLog)} will * return false. */ public synchronized boolean removingOnly() { return !adding; } }