/* * Copyright (c) MuleSoft, Inc. All rights reserved. http://www.mulesoft.com * The software in this package is published under the terms of the CPAL v1.0 * license, a copy of which has been included with this distribution in the * LICENSE.txt file. */ package org.mule.tck.core.util.store; import static java.util.concurrent.TimeUnit.NANOSECONDS; import java.io.Serializable; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentSkipListMap; import org.mule.runtime.api.store.ObjectAlreadyExistsException; import org.mule.runtime.api.store.ObjectDoesNotExistException; import org.mule.runtime.api.store.ObjectStoreException; import org.mule.runtime.core.config.i18n.CoreMessages; import org.mule.runtime.core.util.store.AbstractMonitoredObjectStore; /** * <code>InMemoryObjectStore</code> implements an optionally bounded in-memory store for message IDs with periodic expiry of old * entries. The bounded size is a <i>soft</i> limit and only enforced periodically by the expiry process; this means that the * store may temporarily exceed its maximum size between expiry runs, but will eventually shrink to its configured size. */ public class InMemoryObjectStore<T extends Serializable> extends AbstractMonitoredObjectStore<T> { protected ConcurrentSkipListMap<Long, StoredObject<T>> store; public InMemoryObjectStore() { this.store = new ConcurrentSkipListMap<Long, StoredObject<T>>(); } @Override public boolean isPersistent() { return false; } @Override public boolean contains(Serializable key) throws ObjectStoreException { if (key == null) { throw new ObjectStoreException(CoreMessages.objectIsNull("id")); } synchronized (store) { return store.values().contains(new StoredObject<T>(key, null)); } } @Override public void store(Serializable id, T value) throws ObjectStoreException { if (id == null) { throw new ObjectStoreException(CoreMessages.objectIsNull("id")); } // this block is unfortunately necessary to counter a possible race condition // between multiple nonatomic calls to containsObject/storeObject StoredObject<T> obj = new StoredObject<T>(id, value); synchronized (store) { if (store.values().contains(obj)) { throw new ObjectAlreadyExistsException(); } boolean written = false; while (!written) { Long key = Long.valueOf(System.nanoTime()); written = (store.put(key, obj) == null); } } } @Override @SuppressWarnings("unchecked") public T retrieve(Serializable key) throws ObjectStoreException { synchronized (store) { Map.Entry<?, ?> entry = findEntry(key); if (entry != null) { StoredObject<T> object = (StoredObject<T>) entry.getValue(); return object.getItem(); } } throw new ObjectDoesNotExistException(CoreMessages.objectNotFound(key)); } @SuppressWarnings("unchecked") private Map.Entry<?, ?> findEntry(Serializable key) { Iterator<?> entryIterator = store.entrySet().iterator(); while (entryIterator.hasNext()) { Map.Entry<?, ?> entry = (Map.Entry<?, ?>) entryIterator.next(); StoredObject<T> object = (StoredObject<T>) entry.getValue(); if (object.getId().equals(key)) { return entry; } } return null; } @Override public T remove(Serializable key) throws ObjectStoreException { synchronized (store) { Map.Entry<?, ?> entry = findEntry(key); if (entry != null) { StoredObject<T> removedObject = store.remove(entry.getKey()); return removedObject.getItem(); } } throw new ObjectDoesNotExistException(CoreMessages.objectNotFound(key)); } @Override public void clear() throws ObjectStoreException { synchronized (store) { store.clear(); } } protected int expireAndCount() { // first we trim the store according to max size int expiredEntries = 0; final long now = System.nanoTime(); Map.Entry<?, ?> oldestEntry; purge: while ((oldestEntry = store.firstEntry()) != null) { Long oldestKey = (Long) oldestEntry.getKey(); long oldestKeyValue = oldestKey.longValue(); if (NANOSECONDS.toMillis(now - oldestKeyValue) >= entryTTL) { store.remove(oldestKey); expiredEntries++; } else { break purge; } } return expiredEntries; } protected boolean isTrimNeeded(int currentSize) { return currentSize > maxEntries; } protected boolean isExpirationNeeded() { // this is not guaranteed to be precise, but we don't mind int currentSize = store.size(); // should expire further if entry TTLs are enabled return entryTTL > 0 && currentSize != 0; } protected int doTrimAndExpire() { int expiredEntries = 0; if (isTrimNeeded(store.size())) { expiredEntries += trimToMaxSize(store.size()); } if (isExpirationNeeded()) { expiredEntries = trimToMaxSize(store.size()); expiredEntries += this.expireAndCount(); } return expiredEntries; } @Override public void expire() { int expiredEntries = doTrimAndExpire(); if (logger.isDebugEnabled()) { logger.debug("Expired " + expiredEntries + " old entries"); } } private int trimToMaxSize(int currentSize) { if (maxEntries < 0) { return currentSize; } int excess = (currentSize - maxEntries); if (excess > 0) { while (currentSize > maxEntries) { store.pollFirstEntry(); currentSize--; } if (logger.isDebugEnabled()) { logger.debug("Expired " + excess + " excess entries"); } } return excess; } @Override public String toString() { return getClass().getSimpleName() + " " + store; } /** * Represents the object stored in the store. This class holds the Object itslef and its ID. */ protected static class StoredObject<T> { private Serializable id; private T item; public StoredObject(Serializable id, T item) { this.id = id; this.item = item; } public Serializable getId() { return id; } public T getItem() { return item; } @Override @SuppressWarnings("unchecked") public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } StoredObject<T> that = (StoredObject<T>) o; if (!id.equals(that.id)) { return false; } return true; } @Override public int hashCode() { return id.hashCode(); } @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("StoredObject"); sb.append("{id='").append(id).append('\''); sb.append(", item=").append(item); sb.append('}'); return sb.toString(); } } }