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