/*
* Copyright (c) 2009-2010 Lockheed Martin Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eurekastreams.server.persistence.mappers.cache.testhelpers;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eurekastreams.server.persistence.mappers.cache.Cache;
/**
* Simple Cache implementation for integration tests, storing the cached information in memory, rather than engaging an
* external memcached server.
*/
public class SimpleMemoryCache implements Cache
{
/**
* Map to hold the ModelViews.
*/
private Map<String, Object> cache = new HashMap<String, Object>();
/**
* Maximum number of items to keep in any memcached list.
*/
private static final int MAX_LIST_SIZE = 10000;
/**
* Instance of the logger.
*/
private final Log log = LogFactory.getLog(SimpleMemoryCache.class);
/**
* Clear the cache - call this in the setup method.
*/
public void clear()
{
cache.clear();
}
/**
* {@inheritDoc}
*/
public int getMaximumListSize()
{
return MAX_LIST_SIZE;
}
/**
* Gets a value from the cache.
*
* @param inKey
* the key of the object to get.
* @return the Object from the cache matching inKey (or null if nothing found).
*/
public Object get(final String inKey)
{
return cache.get(inKey);
}
/**
* {@inheritDoc}
*/
public ArrayList<Long> getList(final String inKey)
{
return this.getList(inKey, this.getMaximumListSize());
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public ArrayList<Long> getList(final String inKey, final int inMaximumEntries)
{
ArrayList toReturn = null;
byte[] bytes = (byte[]) cache.get(inKey);
if (bytes != null)
{
try
{
toReturn = getListFromBytes(bytes);
}
catch (IOException e)
{
// problem getting the key .. return a null so the client goes
// to the database
log.error("Unable to retrieve LIST key " + inKey + " from memcached. Exception " + e.getMessage());
return null;
}
}
// check list size and trim if necessary
if (toReturn != null && toReturn.size() > inMaximumEntries)
{
ArrayList<Long> trimmed = (ArrayList<Long>) toReturn.subList(0, inMaximumEntries - 1);
toReturn = trimmed;
try
{
this.set(inKey, getBytesFromList(trimmed));
}
catch (IOException e)
{
log.error("Error getBytesFromList getList memcached list with passed in value for key " + inKey
+ ". Exception : " + e.toString());
}
}
return toReturn;
}
/**
* {@inheritDoc}
*/
public Map<String, Object> multiGet(final Collection<String> inKeys)
{
Map<String, Object> results = new HashMap<String, Object>();
for (String key : inKeys)
{
if (cache.containsKey(key))
{
results.put(key, cache.get(key));
}
}
return results;
}
/**
* {@inheritDoc}
*/
public Map<String, ArrayList<Long>> multiGetList(final Collection<String> inKeys)
{
Map<String, Object> multiGetMap = this.multiGet(inKeys);
Map<String, ArrayList<Long>> toReturn = new HashMap<String, ArrayList<Long>>();
for (String key : multiGetMap.keySet())
{
// look up the value, convert it to an ArrayList and add it to the return
try
{
ArrayList<Long> value = this.getListFromBytes(multiGetMap.get(key));
toReturn.put(key, value);
}
catch (IOException e)
{
log.error("Error getListFromBytes multiGetList memcached list with passed in value for key " + key
+ ". Exception : " + e.toString());
toReturn.put(key, null); // return null to force client to reload
}
}
return toReturn;
}
/**
* {@inheritDoc}
*/
public void set(final String inKey, final Object inValue)
{
if (inValue != null)
{
cache.put(inKey, inValue);
}
else
{
throw new RuntimeException("DO NOT SET NULL, INSTEAD USE DELETE");
}
}
/**
* {@inheritDoc}
*/
public ArrayList<Long> setListCAS(final String inKey, final List<Long> inValue)
{
if (inValue == null)
{
throw new RuntimeException("DO NOT setListCAS NULL, INSTEAD USE DELETE");
}
ArrayList<Long> toReturn = null;
try
{
toReturn = getListFromBytes(cache.get(inKey));
}
catch (IOException e)
{
log.error("Error getListFromBytes setListCAS memcached list with passed in value for key " + inKey
+ ". Exception : " + e.toString());
return null;
}
try
{
cache.put(inKey, getBytesFromList(inValue));
}
catch (IOException e)
{
log.error("Error getBytesFromList setListCAS memcached list with passed in value for key " + inKey
+ ". Exception : " + e.toString());
return null;
}
return toReturn;
}
/**
* {@inheritDoc}
*/
public void setList(final String inKey, final List<Long> inValue)
{
if (inValue == null)
{
throw new RuntimeException("DO NOT setList NULL, INSTEAD USE DELETE");
}
try
{
cache.put(inKey, getBytesFromList(inValue));
}
catch (IOException e)
{
log.error("Error setting memcached list with passed in value for key " + inKey
+ ". Exception : " + e.toString());
}
}
/**
* Get the byte[] from a ArrayList<Long>.
*
* @param inListOfLongs
* the list of longs to convert
* @return the byte[] representation of the ArrayList
* @throws IOException thrown if any errors encountered
*/
protected byte[] getBytesFromList(final List<Long> inListOfLongs) throws IOException
{
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(bytes);
byte[] toReturn = null;
try
{
for (Long oneLong : inListOfLongs)
{
out.writeLong(oneLong);
out.flush();
}
toReturn = bytes.toByteArray();
}
finally
{
out.close();
}
return toReturn;
}
/**
* Convert the memcached object into a List<Long>.
*
* @param inBytesOfLongs
* the byte[] to convert
* @return the byte[] as List<Long>, null if not valid or empty bytes
* @throws IOException thrown if any errors
*/
protected ArrayList<Long> getListFromBytes(final Object inBytesOfLongs) throws IOException
{
if (inBytesOfLongs == null)
{
return null;
}
ArrayList<Long> toReturn = new ArrayList<Long>();
ByteArrayInputStream bytes = new ByteArrayInputStream((byte[]) inBytesOfLongs);
DataInputStream input = new DataInputStream(bytes);
try
{
while (input.available() > 0)
{
toReturn.add(input.readLong());
}
}
finally
{
input.close();
}
return toReturn;
}
/**
* {@inheritDoc}
*/
public void delete(final String inKey)
{
cache.remove(inKey);
}
/**
* {@inheritDoc}
*/
public void deleteList(final String inKey)
{
// for simple memory cache this simply
// calls our existing delete ....
// for 'real' cache this also
// deletes a marker key
this.delete(inKey);
}
/**
* {@inheritDoc}
*/
public void addToTopOfList(final String inKey, final List<Long> inValue)
{
if (inValue == null)
{
throw new RuntimeException("DO NOT addToTopOfList NULL, INSTEAD USE DELETE");
}
ArrayList<Long> existingList = new ArrayList<Long>();
try
{
if (cache.containsKey(inKey))
{
existingList = getListFromBytes(cache.get(inKey));
}
}
catch (IOException e1)
{
log.error("Exception in addToTopOfList getListFromBytes for key " + inKey + e1.toString());
return;
}
ArrayList<Long> listOfLongs = new ArrayList<Long>();
listOfLongs.addAll(inValue); // add new first (prepend)
listOfLongs.addAll(existingList); // add the previously existing list second
try
{
cache.put(inKey, this.getBytesFromList(listOfLongs));
}
catch (IOException e2)
{
log.error("Exception in addToTopOfList getBytesFromList for key " + inKey + e2.toString());
return;
}
}
/**
* {@inheritDoc}
*/
public void addToTopOfList(final String inKey, final Long inValue)
{
ArrayList<Long> longs = new ArrayList<Long>();
longs.add(inValue);
this.addToTopOfList(inKey, longs);
}
/**
* {@inheritDoc}
*/
public void removeFromList(final String inKey, final Long inValue)
{
if (!cache.containsKey(inKey))
{
return;
}
List<Long> list;
try
{
list = getListFromBytes(cache.get(inKey));
list.remove(inValue);
}
catch (IOException e)
{
log.error("Exception in removeFromList getListFromBytes for key " + inKey + e.toString());
return;
}
try
{
cache.put(inKey, getBytesFromList(list));
}
catch (IOException e)
{
log.error("Exception in removeFromList getBytesFromList for key " + inKey + e.toString());
return;
}
}
/**
* {@inheritDoc}
*/
public void removeFromLists(final List<String> inKeys, final List<Long> inValues)
{
Map<String, ArrayList<Long>> results = multiGetList(inKeys);
for (String key : results.keySet())
{
for (long value : inValues)
{
removeFromList(key, value);
}
}
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public Set<Long> addToSet(final String inKey, final Long inValue)
{
if (inValue == null)
{
throw new RuntimeException("DO NOT addToSet NULL, INSTEAD USE DELETE");
}
Set<Long> set;
if (cache.containsKey(inKey))
{
set = (Set<Long>) cache.get(inKey);
}
else
{
set = new HashSet<Long>();
}
set.add(inValue);
cache.put(inKey, set);
return set;
}
/**
* {@inheritDoc}
*/
@SuppressWarnings("unchecked")
public void removeFromSet(final String inKey, final Long inValue)
{
if (inValue == null)
{
throw new RuntimeException("DO NOT removeFromSet NULL, INSTEAD USE DELETE");
}
if (!cache.containsKey(inKey))
{
return;
}
Set<Long> set = (Set<Long>) cache.get(inKey);
set.remove(inValue);
}
}