/* * Copyright (C) 2005-2010 Alfresco Software Limited. * * This file is part of Alfresco * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ package org.alfresco.repo.cache.lookup; import java.util.Map; import java.util.TreeMap; import junit.framework.AssertionFailedError; import junit.framework.TestCase; import org.alfresco.repo.cache.MemoryCache; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.cache.lookup.EntityLookupCache.EntityLookupCallbackDAO; import org.alfresco.util.EqualsHelper; import org.alfresco.util.Pair; /** * A cache for two-way lookups of database entities. These are characterized by having a unique * key (perhaps a database ID) and a separate unique key that identifies the object. *

* The keys must have good equals and hashCode implementations and * must respect the case-sensitivity of the use-case. * * @author Derek Hulley * @since 3.2 */ public class EntityLookupCacheTest extends TestCase implements EntityLookupCallbackDAO { SimpleCache cache; private EntityLookupCache entityLookupCacheA; private EntityLookupCache entityLookupCacheB; private TreeMap database; @Override protected void setUp() throws Exception { cache = new MemoryCache(); entityLookupCacheA = new EntityLookupCache(cache, "A", this); entityLookupCacheB = new EntityLookupCache(cache, "B", this); database = new TreeMap(); } public void testLookupsUsingIncorrectValue() throws Exception { try { // Keep the "database" empty entityLookupCacheA.getByValue(this); } catch (AssertionFailedError e) { // Expected } } public void testLookupAgainstEmpty() throws Exception { TestValue value = new TestValue("AAA"); Pair entityPair = entityLookupCacheA.getByValue(value); assertNull(entityPair); assertTrue(database.isEmpty()); // Now do lookup or create entityPair = entityLookupCacheA.getOrCreateByValue(value); assertNotNull("Expected a value to be found", entityPair); Long entityId = entityPair.getFirst(); assertTrue("Database ID should have been created", database.containsKey(entityId)); assertEquals("Database value incorrect", value.val, database.get(entityId)); // Do lookup or create again entityPair = entityLookupCacheA.getOrCreateByValue(value); assertNotNull("Expected a value to be found", entityPair); assertEquals("Expected same entity ID", entityId, entityPair.getFirst()); // Look it up using the value entityPair = entityLookupCacheA.getByValue(value); assertNotNull("Lookup after create should work", entityPair); // Look it up using the ID entityPair = entityLookupCacheA.getByKey(entityId); assertNotNull("Lookup by key should work after create", entityPair); assertTrue("Looked-up type incorrect", entityPair.getSecond() instanceof TestValue); assertEquals("Looked-up type value incorrect", value, entityPair.getSecond()); } public void testLookupAgainstExisting() throws Exception { // Put some values in the "database" createValue(new TestValue("AAA")); createValue(new TestValue("BBB")); createValue(new TestValue("CCC")); // Look up by value Pair entityPair = entityLookupCacheA.getByValue(new TestValue("AAA")); assertNotNull("Expected value to be found", entityPair); assertEquals("ID is incorrect", new Long(1), entityPair.getFirst()); // Look up by ID entityPair = entityLookupCacheA.getByKey(new Long(2)); assertNotNull("Expected value to be found", entityPair); // Do lookup or create entityPair = entityLookupCacheA.getByValue(new TestValue("CCC")); assertNotNull("Expected value to be found", entityPair); assertEquals("ID is incorrect", new Long(3), entityPair.getFirst()); } public void testRegions() throws Exception { TestValue valueAAA = new TestValue("AAA"); Pair entityPairAAA = entityLookupCacheA.getOrCreateByValue(valueAAA); assertNotNull(entityPairAAA); assertEquals("AAA", database.get(entityPairAAA.getFirst())); assertEquals(2, cache.getKeys().size()); TestValue valueBBB = new TestValue("BBB"); Pair entityPairBBB = entityLookupCacheB.getOrCreateByValue(valueBBB); assertNotNull(entityPairBBB); assertEquals("BBB", database.get(entityPairBBB.getFirst())); assertEquals(4, cache.getKeys().size()); // Now cross-check against the caches and make sure that the cache entityPairBBB = entityLookupCacheA.getByValue(valueBBB); assertEquals(6, cache.getKeys().size()); entityPairBBB = entityLookupCacheB.getByValue(valueAAA); assertEquals(8, cache.getKeys().size()); } public void testNullLookups() throws Exception { TestValue valueNull = null; Pair entityPairNull = entityLookupCacheA.getOrCreateByValue(valueNull); assertNotNull(entityPairNull); assertTrue(database.containsKey(entityPairNull.getFirst())); assertNull(database.get(entityPairNull.getFirst())); assertEquals(2, cache.getKeys().size()); // Look it up again Pair entityPairCheck = entityLookupCacheA.getOrCreateByValue(valueNull); assertNotNull(entityPairNull); assertTrue(database.containsKey(entityPairNull.getFirst())); assertNull(database.get(entityPairNull.getFirst())); assertEquals(entityPairNull, entityPairCheck); } public void testUpdate() throws Exception { TestValue valueOne = new TestValue(getName() + "-ONE"); TestValue valueTwo = new TestValue(getName() + "-TWO"); Pair entityPairOne = entityLookupCacheA.getOrCreateByValue(valueOne); assertNotNull(entityPairOne); Long id = entityPairOne.getFirst(); assertEquals(valueOne.val, database.get(id)); assertEquals(2, cache.getKeys().size()); // Update int updateCount = entityLookupCacheA.updateValue(id, valueTwo); assertEquals("Update count was incorrect.", 1, updateCount); assertEquals(valueTwo.val, database.get(id)); assertEquals(2, cache.getKeys().size()); } public void testDeleteByKey() throws Exception { TestValue valueOne = new TestValue(getName() + "-ONE"); Pair entityPairOne = entityLookupCacheA.getOrCreateByValue(valueOne); assertNotNull(entityPairOne); Long id = entityPairOne.getFirst(); assertEquals(valueOne.val, database.get(id)); assertEquals(2, cache.getKeys().size()); // Delete int deleteCount = entityLookupCacheA.deleteByKey(id); assertEquals("Delete count was incorrect.", 1, deleteCount); assertNull(database.get(id)); assertEquals(0, cache.getKeys().size()); } public void testDeleteByValue() throws Exception { TestValue valueOne = new TestValue(getName() + "-ONE"); Pair entityPairOne = entityLookupCacheA.getOrCreateByValue(valueOne); assertNotNull(entityPairOne); Long id = entityPairOne.getFirst(); assertEquals(valueOne.val, database.get(id)); assertEquals(2, cache.getKeys().size()); // Delete int deleteCount = entityLookupCacheA.deleteByValue(valueOne); assertEquals("Delete count was incorrect.", 1, deleteCount); assertNull(database.get(id)); assertEquals(0, cache.getKeys().size()); } public void testClear() throws Exception { TestValue valueOne = new TestValue(getName() + "-ONE"); Pair entityPairOne = entityLookupCacheA.getOrCreateByValue(valueOne); assertNotNull(entityPairOne); Long id = entityPairOne.getFirst(); assertEquals(valueOne.val, database.get(id)); assertEquals(2, cache.getKeys().size()); // Clear it entityLookupCacheA.clear(); assertEquals(valueOne.val, database.get(id)); // Must still be in database assertEquals(0, cache.getKeys().size()); // ... but cache must be empty } /** * Helper class to represent business object */ private static class TestValue { private final String val; private TestValue(String val) { this.val = val; } @Override public boolean equals(Object obj) { if (obj == null || !(obj instanceof TestValue)) { return false; } return val.equals( ((TestValue)obj).val ); } @Override public int hashCode() { return val.hashCode(); } } public String getValueKey(Object value) { assertNotNull(value); assertTrue(value instanceof TestValue); String dbValue = ((TestValue)value).val; return dbValue; } public Pair findByKey(Long key) { assertNotNull(key); String dbValue = database.get(key); if (dbValue == null) { return null; } // Make a value object TestValue value = new TestValue(dbValue); return new Pair(key, value); } public Pair findByValue(Object value) { assertTrue(value == null || value instanceof TestValue); String dbValue = (value == null) ? null : ((TestValue)value).val; for (Map.Entry entry : database.entrySet()) { if (EqualsHelper.nullSafeEquals(entry.getValue(), dbValue)) { return new Pair(entry.getKey(), entry.getValue()); } } return null; } /** * Simulate creation of a new database entry */ public Pair createValue(Object value) { assertTrue(value == null || value instanceof TestValue); String dbValue = (value == null) ? null : ((TestValue)value).val; // Get the last key Long lastKey = database.isEmpty() ? null : database.lastKey(); Long newKey = null; if (lastKey == null) { newKey = new Long(1); } else { newKey = new Long(lastKey.longValue() + 1); } database.put(newKey, dbValue); return new Pair(newKey, value); } public int updateValue(Long key, Object value) { assertNotNull(key); assertTrue(value == null || value instanceof TestValue); // Find it Pair entityPair = findByKey(key); if (entityPair == null) { return 0; } else { database.put(key, ((TestValue)value).val); return 1; } } public int deleteByKey(Long key) { assertNotNull(key); if (database.containsKey(key)) { database.remove(key); return 1; } else { return 0; } } public int deleteByValue(Object value) { assertTrue(value == null || value instanceof TestValue); // Find it Pair entityPair = findByValue(value); if (entityPair == null) { return 0; } else { database.remove(entityPair.getFirst()); return 1; } } }