All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.keen.client.java.RamEventStore Maven / Gradle / Ivy

There is a newer version: 6.0.0
Show newest version
package io.keen.client.java;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;

/**
 * Implementation of {@link KeenEventStore} which simply keeps a copy of each event in memory until
 * it is explicitly removed.
 * 

* NOTE: This implementation synchronizes all operations in order to ensure thread safety, but as * a result it may perform poorly under high load. For applications that require high throughput, * a custom {@link io.keen.client.java.KeenEventStore} implementation is recommended. * * @author Kevin Litwack ([email protected]) * @since 2.0.0 */ public class RamEventStore implements KeenEventStore { ///// PUBLIC CONSTRUCTORS ///// /** * Constructs a new RAM-based event store. */ public RamEventStore() { collectionIds = new HashMap>(); events = new HashMap(); } ///// KeenEventStore METHODS ///// /** * {@inheritDoc} */ @Override public synchronized Object store(String projectId, String eventCollection, String event) throws IOException { // Create a key from the project ID and event collection. String key = String.format(Locale.US, "%s$%s", projectId, eventCollection); // Get the list of events for the specified key. If no list exists yet, create one. List collectionEvents = collectionIds.get(key); if (collectionEvents == null) { collectionEvents = new ArrayList(); collectionIds.put(key, collectionEvents); } // Remove the oldest events until there is room for at least one more event. while (collectionEvents.size() >= maxEventsPerCollection) { long idToRemove = collectionEvents.remove(0); events.remove(idToRemove); } // Add the event to the event store, add its ID to the collection's list, and return the ID. long id = getNextId(); events.put(id, event); collectionEvents.add(id); return id; } /** * {@inheritDoc} */ @Override public synchronized String get(Object handle) throws IOException { Long id = handleToId(handle); return events.get(id); } /** * {@inheritDoc} */ @Override public synchronized void remove(Object handle) throws IOException { Long id = handleToId(handle); events.remove(id); // Be lazy about removing handles from the collectionIds map - this can happen during the // getHandles call. } /** * {@inheritDoc} */ @Override public synchronized Map> getHandles(String projectId) throws IOException { Map> result = new HashMap>(); for (Map.Entry> entry : collectionIds.entrySet()) { String key = entry.getKey(); // Skip collections for different projects. if (!key.startsWith(projectId)) { continue; } // Extract the collection name from the key. String eventCollection = key.substring(projectId.length() + 1); // Iterate over the list of handles, removing any "dead" events and adding the rest to // the result map. List ids = entry.getValue(); List handles = new ArrayList(); Iterator iterator = ids.iterator(); while (iterator.hasNext()) { Long id = iterator.next(); if (events.get(id) == null) { iterator.remove(); } else { handles.add(id); } } if (handles.size() > 0) { result.put(eventCollection, handles); } } return result; } ///// PUBLIC METHODS ///// /** * Sets the number of events that can be stored for a single collection before aging them out. * * @param maxEventsPerCollection The maximum number of events per collection. */ public void setMaxEventsPerCollection(int maxEventsPerCollection) { this.maxEventsPerCollection = maxEventsPerCollection; } ///// TEST HOOKS ///// /** * Clears all events from the store, effectively resetting it to its initial state. This method * is intended for use during unit testing, and should generally not be called by production * code. */ void clear() { nextId = 0; collectionIds = new HashMap>(); events = new HashMap(); } ///// PRIVATE FIELDS ///// private long nextId = 0; private Map> collectionIds; private Map events; private int maxEventsPerCollection = 10000; ///// PRIVATE METHODS ///// /** * Gets the next ID to use as a handle for a stored event. This implementation just checks for * the next unused ID based on an incrementing counter. *

* NOTE: For long-running processes it's possible that the nextId field will overflow. Hence it * is necessary to handle collisions gracefully by skipping them. * * @return The next ID that should be used to store an event. */ private long getNextId() { // It should be all but impossible for the event cache to grow bigger than Long.MAX_VALUE, // but just for the sake of safe coding practices, check anyway. if (events.size() > Long.MAX_VALUE) { throw new IllegalStateException("Event store exceeded maximum size"); } // Iterate through IDs, starting with the next ID counter, until an unused one is found. long id = nextId; while (events.get(id) != null) { id++; } // Set the next ID to the ID that was found plus one, then return the ID. nextId = id + 1; return id; } /** * Converts an opaque handle into a long ID. If the handle is not a Long, this will throw an * {@link java.lang.IllegalArgumentException}. * * @param handle The handle to convert to an ID. * @return The ID. */ private Long handleToId(Object handle) { if (handle instanceof Long) { return (Long) handle; } else { throw new IllegalArgumentException("Expected handle to be a Long, but was: " + handle.getClass().getCanonicalName()); } } }