├── .gitignore ├── src ├── test │ ├── groovy │ │ └── com │ │ │ └── googlecode │ │ │ └── hibernate │ │ │ └── memcached │ │ │ ├── BaseTestCase.groovy │ │ │ ├── LoggingConfig.groovy │ │ │ ├── AbstractKeyStrategyTestCase.groovy │ │ │ ├── MockMemcached.groovy │ │ │ ├── StringKeyStrategyTest.groovy │ │ │ ├── Md5KeyStrategyTest.groovy │ │ │ ├── Sha1KeyStrategyTest.groovy │ │ │ ├── utils │ │ │ └── StringUtilsTest.groovy │ │ │ ├── HashCodeKeyStrategyTest.groovy │ │ │ ├── spymemcached │ │ │ ├── SpyMemcacheTest.groovy │ │ │ └── SpyMemcacheClientFactoryTest.groovy │ │ │ ├── MemcachedCacheTest.groovy │ │ │ ├── LoggingMemcacheExceptionHandlerTest.groovy │ │ │ ├── MemcachedProviderTest.groovy │ │ │ ├── PropertiesHelperTest.groovy │ │ │ └── ConfigTest.groovy │ └── java │ │ └── com │ │ └── googlecode │ │ └── hibernate │ │ └── memcached │ │ ├── integration │ │ ├── Contact.java │ │ ├── AbstractHibernateTestCase.java │ │ └── ContactIntegrationTest.java │ │ └── MockAppender.java └── main │ └── java │ └── com │ └── googlecode │ └── hibernate │ └── memcached │ ├── KeyStrategy.java │ ├── Md5KeyStrategy.java │ ├── Sha1KeyStrategy.java │ ├── MemcacheClientFactory.java │ ├── MemcacheExceptionHandler.java │ ├── Memcache.java │ ├── StringKeyStrategy.java │ ├── HashCodeKeyStrategy.java │ ├── DigestKeyStrategy.java │ ├── LoggingMemcacheExceptionHandler.java │ ├── PropertiesHelper.java │ ├── dangamemcached │ ├── SimpleErrorHandler.java │ ├── DangaMemcache.java │ └── DangaMemcacheClientFactory.java │ ├── AbstractKeyStrategy.java │ ├── utils │ └── StringUtils.java │ ├── spymemcached │ ├── SpyMemcache.java │ └── SpyMemcacheClientFactory.java │ ├── Config.java │ ├── MemcachedCacheProvider.java │ └── MemcachedCache.java ├── README.md ├── LICENSE.txt └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | hibernate-memcached.ipr 2 | hibernate-memcached.iws 3 | hibernate-memcached.iml 4 | target 5 | release.properties 6 | pom.xml.releaseBackup 7 | nb-configuration.xml 8 | *.iml 9 | *.ipr 10 | *.iws 11 | .idea 12 | *.swp 13 | -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/BaseTestCase.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached 2 | /** 3 | * DOCUMENT ME! 4 | * @author Ray Krueger 5 | */ 6 | abstract class BaseTestCase extends groovy.util.GroovyTestCase { 7 | 8 | static { 9 | LoggingConfig.initializeLogging() 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/KeyStrategy.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | /** 4 | * Strategy interface for parsing the parts used by {@link MemcachedCache} to generate cache keys. 5 | * 6 | * @author Ray Krueger 7 | */ 8 | public interface KeyStrategy { 9 | 10 | String toKey(String regionName, long clearIndex, Object key); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/Md5KeyStrategy.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | import com.googlecode.hibernate.memcached.utils.StringUtils; 4 | 5 | /** 6 | * @author Ray Krueger 7 | */ 8 | public class Md5KeyStrategy extends DigestKeyStrategy { 9 | protected String digest(String key) { 10 | return StringUtils.md5Hex(key); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/Sha1KeyStrategy.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | import com.googlecode.hibernate.memcached.utils.StringUtils; 4 | 5 | /** 6 | * @author Ray Krueger 7 | */ 8 | public class Sha1KeyStrategy extends DigestKeyStrategy { 9 | protected String digest(String key) { 10 | return StringUtils.sha1Hex(key); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/MemcacheClientFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | /** 4 | * Simple interface used to abstract the creation of the MemcachedClient 5 | * All implementers must have a constructor that takes an instance of 6 | * {@link com.googlecode.hibernate.memcached.PropertiesHelper}. 7 | * 8 | * @author Ray Krueger 9 | */ 10 | public interface MemcacheClientFactory { 11 | 12 | Memcache createMemcacheClient() throws Exception; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/MemcacheExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | /** 4 | * @author Ray Krueger 5 | */ 6 | public interface MemcacheExceptionHandler { 7 | 8 | void handleErrorOnGet(String key, Exception e); 9 | 10 | void handleErrorOnSet(String key, int cacheTimeSeconds, Object o, Exception e); 11 | 12 | void handleErrorOnDelete(String key, Exception e); 13 | 14 | void handleErrorOnIncr(String key, int factor, int startingValue, Exception e); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/LoggingConfig.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached 2 | 3 | import org.apache.log4j.BasicConfigurator 4 | 5 | /** 6 | * DOCUMENT ME! 7 | * @author Ray Krueger 8 | */ 9 | class LoggingConfig { 10 | 11 | static { 12 | System.setProperty "net.spy.log.LoggerImpl", "net.spy.log.Log4JLogger" 13 | } 14 | 15 | public static void initializeLogging() { 16 | BasicConfigurator.resetConfiguration(); 17 | BasicConfigurator.configure(); 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/Memcache.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Interface to abstract memcache operations. 7 | * 8 | * @author Ray Krueger 9 | */ 10 | public interface Memcache { 11 | 12 | Object get(String key); 13 | 14 | Map getMulti(String... keys); 15 | 16 | void set(String key, int cacheTimeSeconds, Object o); 17 | 18 | void delete(String key); 19 | 20 | void incr(String key, int factor, int startingValue); 21 | 22 | void shutdown(); 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/StringKeyStrategy.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | /** 4 | * Simply transforms the key object using String.valueOf() 5 | * 6 | * @deprecated As of 1.3 use the Sha1KeyStrategy instead 7 | * @author Ray Krueger 8 | */ 9 | @Deprecated 10 | public class StringKeyStrategy extends AbstractKeyStrategy { 11 | 12 | protected String transformKeyObject(Object key) { 13 | String stringKey = String.valueOf(key); 14 | log.debug("Transformed key [{}] to string [{}]", key, stringKey); 15 | return stringKey; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/HashCodeKeyStrategy.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | /** 4 | * Transforms the key object using key.hashCode() 5 | * 6 | * @deprecated as of 1.3 HashCodeKeyStrategy is deprecated and Sha1KeyStrategy is the default. Use that instead. 7 | * @author Ray Krueger 8 | */ 9 | @Deprecated 10 | public class HashCodeKeyStrategy extends AbstractKeyStrategy { 11 | 12 | protected String transformKeyObject(Object key) { 13 | int hashCode = key.hashCode(); 14 | log.debug("Transformed key [{}] to hashCode [{}]", key, hashCode); 15 | return String.valueOf(hashCode); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/DigestKeyStrategy.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | import com.googlecode.hibernate.memcached.utils.StringUtils; 4 | 5 | /** 6 | * @author Ray Krueger 7 | */ 8 | public abstract class DigestKeyStrategy extends AbstractKeyStrategy { 9 | 10 | protected String transformKeyObject(Object key) { 11 | return key.toString() + ":" + key.hashCode(); 12 | } 13 | 14 | protected String concatenateKey(String regionName, long clearIndex, Object key) { 15 | String longKey = super.concatenateKey(regionName, clearIndex, key); 16 | return digest(longKey); 17 | } 18 | 19 | protected abstract String digest(String string); 20 | } 21 | -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/AbstractKeyStrategyTestCase.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached 2 | 3 | import org.junit.Test 4 | 5 | /** 6 | * DOCUMENT ME! 7 | * 8 | * @author Ray Krueger 9 | */ 10 | abstract class AbstractKeyStrategyTestCase extends BaseTestCase { 11 | 12 | protected KeyStrategy strategy 13 | 14 | protected void setUp() { 15 | strategy = getKeyStrategy() 16 | } 17 | 18 | void assert_cache_key_equals (expected, namespace, clearIndex, keyObject) { 19 | String key = strategy.toKey(namespace, clearIndex, keyObject) 20 | assertEquals(expected, key) 21 | } 22 | 23 | void test_assert_null_key_does_not_validate() { 24 | try { 25 | strategy.toKey(null, 0, null) 26 | fail(IllegalArgumentException.class.name + " expected."); 27 | } catch (IllegalArgumentException expected) { 28 | } 29 | } 30 | 31 | abstract KeyStrategy getKeyStrategy(); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/MockMemcached.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached 2 | /** 3 | * DOCUMENT ME! 4 | * @author Ray Krueger 5 | */ 6 | class MockMemcached implements Memcache { 7 | 8 | def cache = [:] 9 | 10 | public Object get(String key) { 11 | cache[key] 12 | } 13 | 14 | public void set(String key, int cacheTimeSeconds, Object o) { 15 | cache[key] = o 16 | } 17 | 18 | public void delete(String key) { 19 | cache.remove key 20 | } 21 | 22 | public void incr(String key, int factor, int startingValue) { 23 | Integer counter = (Integer) cache[key] 24 | if (counter != null) { 25 | cache[key] = counter + 1 26 | } else { 27 | cache[key] = counter 28 | } 29 | } 30 | 31 | public void shutdown() { 32 | 33 | } 34 | 35 | 36 | public Map getMulti(String[] keys) { 37 | return cache.findAll {key, value -> keys.toList().contains(key)} 38 | } 39 | } -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/StringKeyStrategyTest.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached 2 | 3 | /** 4 | * DOCUMENT ME! 5 | * 6 | * @author Ray Krueger 7 | */ 8 | class StringKeyStrategyTest extends AbstractKeyStrategyTestCase { 9 | 10 | public KeyStrategy getKeyStrategy() { 11 | new StringKeyStrategy() 12 | } 13 | 14 | void test() { 15 | assert_cache_key_equals "test:0:boing", "test", 0, "boing" 16 | } 17 | 18 | void test_null_region() { 19 | assert_cache_key_equals "null:0:boing", null, 0, "boing" 20 | } 21 | 22 | void test_spaces() { 23 | assert_cache_key_equals "Ihavespaces:0:sodoI", "I have spaces", 0, "so do I" 24 | } 25 | 26 | void test_really_long_key_throws_exception() { 27 | String regionName = "" 28 | 250.times {regionName += "x"} 29 | shouldFail(IllegalArgumentException) { 30 | getKeyStrategy().toKey(regionName, 0, "blah blah blah") 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/Md5KeyStrategyTest.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached 2 | 3 | /** 4 | * DOCUMENT ME! 5 | * 6 | * @author Ray Krueger 7 | */ 8 | class Md5KeyStrategyTest extends AbstractKeyStrategyTestCase { 9 | 10 | public KeyStrategy getKeyStrategy() { 11 | return new Md5KeyStrategy() 12 | } 13 | 14 | void test() { 15 | assert_cache_key_equals "a088ce3b48a12c8a8f26058240f4518d", "test", 0, "boing" 16 | } 17 | 18 | void test_null_region() { 19 | assert_cache_key_equals "cf23c7bb0c99979d4be1129adc959e6f", null, 0, "boing" 20 | } 21 | 22 | void test_spaces() { 23 | assert_cache_key_equals "0564810c2fd4e86dc6f355ad99e7d01b", "I have spaces", 0, "so do I" 24 | } 25 | 26 | void test_really_long_keys_get_truncated() { 27 | String regionName = "" 28 | 250.times {regionName += "x"} 29 | assert_cache_key_equals "16df3d87c2f8bde43fcdbb545be10626", regionName, 0, "blah blah blah" 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/Sha1KeyStrategyTest.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached 2 | 3 | /** 4 | * DOCUMENT ME! 5 | * 6 | * @author Ray Krueger 7 | */ 8 | class Sha1KeyStrategyTest extends AbstractKeyStrategyTestCase { 9 | 10 | public KeyStrategy getKeyStrategy() { 11 | return new Sha1KeyStrategy() 12 | } 13 | 14 | void test() { 15 | assert_cache_key_equals "cd23e26dd7ab1d052e1c0a04daa27a03f6cd5d1c", "test", 0, "boing" 16 | } 17 | 18 | void test_null_region() { 19 | assert_cache_key_equals "6afcec5614479d46a1ec6d73dabbc2cea154da3c", null, 0, "boing" 20 | } 21 | 22 | void test_spaces() { 23 | assert_cache_key_equals "949b2a6fce917d85bd56e6197c93b3affa694e50", "I have spaces", 0, "so do I" 24 | } 25 | 26 | void test_really_long_keys_get_truncated() { 27 | String regionName = "" 28 | 250.times {regionName += "x"} 29 | assert_cache_key_equals "7f00c6faf1fefaf62cabb512285cc60ce641d5c8", regionName, 0, "blah blah blah" 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/utils/StringUtilsTest.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached.utils 2 | 3 | import com.googlecode.hibernate.memcached.BaseTestCase 4 | 5 | class StringUtilsTest extends BaseTestCase { 6 | 7 | void test_simple_join() { 8 | assertEquals "1, 2, 3", StringUtils.join([1, 2, 3] as Object[], ", ") 9 | } 10 | 11 | void test_empty_join() { 12 | assertEquals "", StringUtils.join([] as Object[], ", ") 13 | } 14 | 15 | void test_null_join () { 16 | assertNull StringUtils.join(null, ",") 17 | } 18 | 19 | void test_md5_hex () { 20 | assertEquals "eae4b23daa656ea031c2b90106304cf2", StringUtils.md5Hex("boosh! and/or kakow") 21 | } 22 | 23 | void test_sha1_hex() { 24 | assertEquals "f18f2dcf68655fe9112ac57c62931cc490c3397c", StringUtils.sha1Hex("boosh! and/or kakow") 25 | } 26 | 27 | void test_null_md5_hex () { 28 | shouldFail(IllegalArgumentException) { 29 | StringUtils.md5Hex(null) 30 | } 31 | } 32 | 33 | 34 | } -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/HashCodeKeyStrategyTest.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached 2 | 3 | import org.junit.Test 4 | 5 | /** 6 | * DOCUMENT ME! 7 | * 8 | * @author Ray Krueger 9 | */ 10 | class HashCodeKeyStrategyTest extends AbstractKeyStrategyTestCase { 11 | 12 | public KeyStrategy getKeyStrategy() { 13 | return new HashCodeKeyStrategy() 14 | } 15 | 16 | void test() { 17 | assert_cache_key_equals "test:0:93916277", "test", 0, "boing" 18 | } 19 | 20 | void test_null_region() { 21 | assert_cache_key_equals "null:0:93916277", null, 0, "boing" 22 | } 23 | 24 | void test_spaces() { 25 | assert_cache_key_equals "Ihavespaces:0:-2100783816", "I have spaces", 0, "so do I" 26 | } 27 | 28 | void test_really_long_key_throws_exception() { 29 | String regionName = "" 30 | 250.times {regionName += "x"} 31 | shouldFail(IllegalArgumentException) { 32 | getKeyStrategy().toKey(regionName, 0, "blah blah blah") 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/LoggingMemcacheExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | /** 7 | * @author Ray Krueger 8 | */ 9 | public class LoggingMemcacheExceptionHandler implements MemcacheExceptionHandler { 10 | 11 | private static final Logger log = LoggerFactory.getLogger(LoggingMemcacheExceptionHandler.class); 12 | 13 | public void handleErrorOnGet(String key, Exception e) { 14 | log.warn("Cache 'get' failed for key [" + key + "]", e); 15 | } 16 | 17 | public void handleErrorOnSet(String key, int cacheTimeSeconds, Object o, Exception e) { 18 | log.warn("Cache 'set' failed for key [" + key + "]", e); 19 | } 20 | 21 | public void handleErrorOnDelete(String key, Exception e) { 22 | log.warn("Cache 'delete' failed for key [" + key + "]", e); 23 | } 24 | 25 | public void handleErrorOnIncr(String key, int factor, int startingValue, Exception e) { 26 | log.warn("Cache 'incr' failed for key [" + key + "]", e); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/spymemcached/SpyMemcacheTest.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached.spymemcached 2 | 3 | import com.googlecode.hibernate.memcached.BaseTestCase 4 | import com.googlecode.hibernate.memcached.MemcachedCache 5 | import com.googlecode.hibernate.memcached.spymemcached.SpyMemcache 6 | import net.spy.memcached.AddrUtil 7 | import net.spy.memcached.MemcachedClient 8 | 9 | /** 10 | * DOCUMENT ME! 11 | * @author Ray Krueger 12 | */ 13 | class SpyMemcacheTest extends BaseTestCase { 14 | 15 | MemcachedCache cache 16 | MemcachedClient client 17 | 18 | protected void setUp() { 19 | client = new MemcachedClient(AddrUtil.getAddresses("localhost:11211")) 20 | cache = new MemcachedCache("MemcachedCacheTest", new SpyMemcache(client)) 21 | } 22 | 23 | protected void tearDown() { 24 | client.shutdown() 25 | } 26 | 27 | void test() { 28 | cache.put("test", "value") 29 | Thread.sleep(100) 30 | assertEquals("value", cache.get("test")) 31 | } 32 | 33 | void test_clear() { 34 | cache.setClearSupported(true) 35 | cache.put("test", "value") 36 | Thread.sleep(100) 37 | assertEquals("value", cache.get("test")) 38 | cache.clear() 39 | Thread.sleep(100) 40 | assertNull(cache.get("test")) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/PropertiesHelper.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | import java.util.Properties; 4 | 5 | public class PropertiesHelper { 6 | 7 | private Properties properties; 8 | 9 | public PropertiesHelper(Properties properties) { 10 | this.properties = properties; 11 | } 12 | 13 | public String get(String key) { 14 | return properties.getProperty(key); 15 | } 16 | 17 | public String get(String key, String defaultVal) { 18 | String val = get(key); 19 | return val == null ? defaultVal : val; 20 | } 21 | 22 | public String findValue(String... keys) { 23 | for (String key : keys) { 24 | String value = get(key); 25 | if (value != null) { 26 | return value; 27 | } 28 | } 29 | return null; 30 | } 31 | 32 | public boolean getBoolean(String key, boolean defaultVal) { 33 | String val = get(key); 34 | return val == null ? defaultVal : Boolean.parseBoolean(val); 35 | } 36 | 37 | public long getLong(String key, long defaultVal) { 38 | String val = get(key); 39 | return val == null ? defaultVal : Long.parseLong(val); 40 | } 41 | 42 | public int getInt(String key, int defaultVal) { 43 | return (int) getLong(key, defaultVal); 44 | } 45 | 46 | public double getDouble(String key, double defaultVal) { 47 | String val = get(key); 48 | return val == null ? defaultVal : Double.parseDouble(val); 49 | } 50 | 51 | public > T getEnum(String key, Class type, T defaultValue) { 52 | String val = get(key); 53 | return val == null ? defaultValue : Enum.valueOf(type, val); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/hibernate/memcached/integration/Contact.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached.integration; 2 | 3 | import org.hibernate.annotations.Cache; 4 | import org.hibernate.annotations.CacheConcurrencyStrategy; 5 | import org.hibernate.annotations.Type; 6 | 7 | import javax.persistence.Entity; 8 | import javax.persistence.GeneratedValue; 9 | import javax.persistence.Id; 10 | import java.util.Date; 11 | 12 | 13 | /** 14 | * @author Ray Krueger 15 | */ 16 | @Entity 17 | @Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE) 18 | public class Contact { 19 | 20 | @Id 21 | @GeneratedValue 22 | Long id; 23 | 24 | String firstName; 25 | 26 | String lastName; 27 | 28 | @Type(type = "date") 29 | Date birthday; 30 | 31 | Long getId() { 32 | return id; 33 | } 34 | 35 | void setId(Long id) { 36 | this.id = id; 37 | } 38 | 39 | String getFirstName() { 40 | return firstName; 41 | } 42 | 43 | void setFirstName(String firstName) { 44 | this.firstName = firstName; 45 | } 46 | 47 | String getLastName() { 48 | return lastName; 49 | } 50 | 51 | void setLastName(String lastName) { 52 | this.lastName = lastName; 53 | } 54 | 55 | public Date getBirthday() { 56 | return birthday; 57 | } 58 | 59 | public void setBirthday(Date birthday) { 60 | this.birthday = birthday; 61 | } 62 | 63 | public boolean equals(Object o) { 64 | if (this == o) return true; 65 | if (o == null || getClass() != o.getClass()) return false; 66 | 67 | Contact contact = (Contact) o; 68 | 69 | if (id != null ? !id.equals(contact.id) : contact.id != null) return false; 70 | 71 | return true; 72 | } 73 | 74 | public int hashCode() { 75 | return (id != null ? id.hashCode() : 0); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/MemcachedCacheTest.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached 2 | /** 3 | * DOCUMENT ME! 4 | * @author Ray Krueger 5 | */ 6 | class MemcachedCacheTest extends BaseTestCase { 7 | 8 | MemcachedCache cache 9 | 10 | void test_basics() { 11 | cache = new MemcachedCache("region", new MockMemcached()) 12 | assertNull cache.get("test") 13 | 14 | cache.put "test", "value" 15 | assertEquals "value", cache.get("test") 16 | 17 | cache.update "test", "blah" 18 | assertEquals "blah", cache.read("test") 19 | 20 | cache.remove "test" 21 | assertNull cache.get("test") 22 | 23 | } 24 | 25 | void test_dogpile_cache_miss() { 26 | MockMemcached mockCache = new MockMemcached() 27 | cache = new MemcachedCache("region", mockCache) 28 | cache.setKeyStrategy(new HashCodeKeyStrategy()); 29 | cache.dogpilePreventionEnabled = true 30 | cache.cacheTimeSeconds = 1 31 | cache.dogpilePreventionExpirationFactor = 2 32 | 33 | assertNull cache.get("test") 34 | assertEquals MemcachedCache.DOGPILE_TOKEN, mockCache.cache["region:0:3556498.dogpileTokenKey"] 35 | 36 | cache.put("test", "value") 37 | assertEquals "value", mockCache.cache["region:0:3556498"] 38 | } 39 | 40 | void test_dogpile_cache_hit() { 41 | MockMemcached mockCache = new MockMemcached() 42 | cache = new MemcachedCache("region", mockCache) 43 | cache.setKeyStrategy(new HashCodeKeyStrategy()); 44 | cache.dogpilePreventionEnabled = true 45 | cache.cacheTimeSeconds = 1 46 | cache.dogpilePreventionExpirationFactor = 2 47 | 48 | cache.put("test", "value") 49 | assertEquals "value", mockCache.cache["region:0:3556498"] 50 | assertEquals MemcachedCache.DOGPILE_TOKEN, mockCache.cache["region:0:3556498.dogpileTokenKey"] 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/dangamemcached/SimpleErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached.dangamemcached; 2 | 3 | import com.danga.MemCached.ErrorHandler; 4 | import com.danga.MemCached.MemCachedClient; 5 | import com.googlecode.hibernate.memcached.utils.StringUtils; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | 9 | /** 10 | * DOCUMENT ME! 11 | * 12 | * @author George Wei 13 | */ 14 | public class SimpleErrorHandler implements ErrorHandler { 15 | private static final Logger log = LoggerFactory.getLogger(SimpleErrorHandler.class); 16 | 17 | public void handleErrorOnDelete(MemCachedClient client, Throwable error, 18 | String cacheKey) { 19 | log.error("Error on delete cacheKey [{}]: {}", cacheKey, error); 20 | } 21 | 22 | public void handleErrorOnFlush(MemCachedClient client, Throwable error) { 23 | log.error("Error on flush: {}", error); 24 | } 25 | 26 | public void handleErrorOnGet(MemCachedClient client, Throwable error, 27 | String cacheKey) { 28 | log.error("Error on get cacheKey [{}]: {}", cacheKey, error); 29 | } 30 | 31 | public void handleErrorOnGet(MemCachedClient client, Throwable error, 32 | String[] cacheKeys) { 33 | handleErrorOnGet(client, error, StringUtils.join(cacheKeys, ", ")); 34 | } 35 | 36 | public void handleErrorOnInit(MemCachedClient client, Throwable error) { 37 | log.error("Error on initialization: {}", error); 38 | } 39 | 40 | public void handleErrorOnSet(MemCachedClient client, Throwable error, 41 | String cacheKey) { 42 | log.error("Error on set cacheKey [{}]: {}", cacheKey, error); 43 | } 44 | 45 | public void handleErrorOnStats(MemCachedClient client, Throwable error) { 46 | log.error("Error on stats: {}", error); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/AbstractKeyStrategy.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.regex.Pattern; 7 | 8 | /** 9 | * KeyStrategy base class that handles concatenation, cleaning, and truncating the final cache key. 10 | *

11 | * Concatenates the three key components; regionName, clearIndex and key.
12 | * Subclasses are responsible for transforming the Key object into something identifyable. 13 | * 14 | * @author Ray Krueger 15 | */ 16 | public abstract class AbstractKeyStrategy implements KeyStrategy { 17 | 18 | public static final int MAX_KEY_LENGTH = 250; 19 | 20 | protected final Logger log = LoggerFactory.getLogger(getClass()); 21 | 22 | private static final Pattern CLEAN_PATTERN = Pattern.compile("\\s"); 23 | 24 | public String toKey(String regionName, long clearIndex, Object key) { 25 | if (key == null) { 26 | throw new IllegalArgumentException("key must not be null"); 27 | } 28 | 29 | String keyString = concatenateKey(regionName, clearIndex, transformKeyObject(key)); 30 | 31 | if (keyString.length() > MAX_KEY_LENGTH) { 32 | throw new IllegalArgumentException("Key is longer than " + MAX_KEY_LENGTH + " characters, try using the Sha1KeyStrategy: " + keyString); 33 | } 34 | 35 | String finalKey = CLEAN_PATTERN.matcher(keyString).replaceAll(""); 36 | log.debug("Final cache key: [{}]", finalKey); 37 | return finalKey; 38 | } 39 | 40 | protected abstract String transformKeyObject(Object key); 41 | 42 | protected String concatenateKey(String regionName, long clearIndex, Object key) { 43 | return new StringBuilder() 44 | .append(regionName) 45 | .append(":") 46 | .append(clearIndex) 47 | .append(":") 48 | .append(String.valueOf(key)).toString(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/LoggingMemcacheExceptionHandlerTest.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached 2 | 3 | import org.apache.log4j.Appender 4 | import org.apache.log4j.Layout 5 | import org.apache.log4j.Logger 6 | import org.apache.log4j.spi.ErrorHandler 7 | import org.apache.log4j.spi.Filter 8 | import org.apache.log4j.spi.LoggingEvent 9 | import org.junit.Assert 10 | 11 | /** 12 | * This test is lame, I have no idea what I should do to make it better. 13 | * @author Ray Krueger 14 | */ 15 | class LoggingMemcacheExceptionHandlerTest extends BaseTestCase { 16 | 17 | def handler = new LoggingMemcacheExceptionHandler() 18 | Logger logger = Logger.getLogger(LoggingMemcacheExceptionHandler) 19 | 20 | protected void setUp() { 21 | logger.removeAllAppenders() 22 | } 23 | 24 | void testDelete() { 25 | Exception exception = new Exception("blah") 26 | def appender = new MockAppender("Cache 'delete' failed for key [blah]", exception) 27 | logger.addAppender appender 28 | handler.handleErrorOnDelete "blah", exception 29 | assert appender.appenderCalled 30 | } 31 | 32 | void testGet() { 33 | Exception exception = new Exception("blah") 34 | def appender = new MockAppender("Cache 'get' failed for key [blah]", exception) 35 | logger.addAppender appender 36 | handler.handleErrorOnGet "blah", exception 37 | assert appender.appenderCalled 38 | } 39 | 40 | void testIncr() { 41 | Exception exception = new Exception("blah") 42 | def appender = new MockAppender("Cache 'incr' failed for key [blah]", exception) 43 | logger.addAppender appender 44 | handler.handleErrorOnIncr "blah", 10, 20, exception 45 | assert appender.appenderCalled 46 | } 47 | 48 | void testSet() { 49 | Exception exception = new Exception("blah") 50 | def appender = new MockAppender("Cache 'set' failed for key [blah]", exception) 51 | logger.addAppender appender 52 | handler.handleErrorOnSet "blah", 300, new Object(), exception 53 | assert appender.appenderCalled 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/MemcachedProviderTest.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | /** 4 | * DOCUMENT ME! 5 | * 6 | * @author Ray Krueger 7 | */ 8 | class MemcachedProviderTest extends BaseTestCase { 9 | 10 | MemcachedCacheProvider provider 11 | 12 | void setUp() { 13 | provider = new MemcachedCacheProvider() 14 | } 15 | 16 | void test_defaults() { 17 | Properties properties = new Properties() 18 | provider.start(properties) 19 | MemcachedCache cache = (MemcachedCache) provider.buildCache("test", properties) 20 | assertNotNull(cache) 21 | 22 | //assert Defaults 23 | assertFalse(cache.isClearSupported()) 24 | assertEquals(300, cache.getCacheTimeSeconds()) 25 | assertEquals Sha1KeyStrategy.class, cache.getKeyStrategy().class 26 | } 27 | 28 | void test_region_properties() { 29 | Properties properties = new Properties() 30 | 31 | properties.setProperty "hibernate.memcached.serverList", "127.0.0.1:11211" 32 | properties.setProperty "hibernate.memcached.test.cacheTimeSeconds", "500" 33 | properties.setProperty "hibernate.memcached.test.clearSupported", "true" 34 | properties.setProperty "hibernate.memcached.test.keyStrategy", StringKeyStrategy.class.getName() 35 | 36 | provider.start(properties) 37 | MemcachedCache cache = (MemcachedCache) provider.buildCache("test", properties) 38 | assertNotNull(cache) 39 | 40 | //assert Defaults 41 | assertTrue(cache.isClearSupported()) 42 | assertEquals(500, cache.getCacheTimeSeconds()) 43 | assertEquals(StringKeyStrategy.class, cache.getKeyStrategy().class) 44 | } 45 | 46 | void test_string_key_strategy() { 47 | Properties properties = new Properties() 48 | 49 | properties.setProperty("hibernate.memcached.keyStrategy", StringKeyStrategy.class.getName()) 50 | 51 | provider.start(properties) 52 | MemcachedCache cache = (MemcachedCache) provider.buildCache("test", properties) 53 | assertNotNull(cache) 54 | } 55 | 56 | void tearDown() { 57 | provider.stop() 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/hibernate/memcached/MockAppender.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | import junit.framework.Assert; 4 | import org.apache.log4j.Appender; 5 | import org.apache.log4j.Layout; 6 | import org.apache.log4j.spi.ErrorHandler; 7 | import org.apache.log4j.spi.Filter; 8 | import org.apache.log4j.spi.LoggingEvent; 9 | 10 | /** 11 | * @author Ray Krueger 12 | */ 13 | class MockAppender implements Appender { 14 | 15 | String expectedMessage; 16 | Exception expectedError; 17 | boolean appenderCalled = false; 18 | 19 | MockAppender(String expectedMessage, Exception expectedError) { 20 | this.expectedMessage = expectedMessage; 21 | this.expectedError = expectedError; 22 | } 23 | 24 | public void addFilter(Filter newFilter) { 25 | throw new UnsupportedOperationException(); 26 | } 27 | 28 | public Filter getFilter() { 29 | throw new UnsupportedOperationException(); 30 | } 31 | 32 | public void clearFilters() { 33 | throw new UnsupportedOperationException(); 34 | } 35 | 36 | public void close() { 37 | } 38 | 39 | public void doAppend(LoggingEvent event) { 40 | Assert.assertEquals(expectedMessage, event.getMessage()); 41 | Assert.assertEquals(expectedError, event.getThrowableInformation().getThrowable()); 42 | appenderCalled = true; 43 | } 44 | 45 | public String getName() { 46 | throw new UnsupportedOperationException(); 47 | } 48 | 49 | public void setErrorHandler(ErrorHandler errorHandler) { 50 | throw new UnsupportedOperationException(); 51 | } 52 | 53 | public ErrorHandler getErrorHandler() { 54 | throw new UnsupportedOperationException(); 55 | } 56 | 57 | public void setLayout(Layout layout) { 58 | throw new UnsupportedOperationException(); 59 | } 60 | 61 | public Layout getLayout() { 62 | throw new UnsupportedOperationException(); 63 | } 64 | 65 | public void setName(String name) { 66 | throw new UnsupportedOperationException(); 67 | } 68 | 69 | public boolean requiresLayout() { 70 | throw new UnsupportedOperationException(); 71 | } 72 | } -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/utils/StringUtils.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached.utils; 2 | 3 | import java.security.MessageDigest; 4 | import java.security.NoSuchAlgorithmException; 5 | 6 | /** 7 | * @author Ray Krueger 8 | */ 9 | public class StringUtils { 10 | 11 | private static final char[] DIGITS = { 12 | '0', '1', '2', '3', '4', '5', '6', '7', 13 | '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' 14 | }; 15 | 16 | public static String join(Object[] array, String separator) { 17 | if (array == null) { 18 | return null; 19 | } 20 | int arraySize = array.length; 21 | StringBuilder buffer = new StringBuilder(); 22 | 23 | for (int i = 0; i < arraySize; i++) { 24 | if (i > 0) { 25 | buffer.append(separator); 26 | } 27 | if (array[i] != null) { 28 | buffer.append(array[i]); 29 | } 30 | } 31 | return buffer.toString(); 32 | } 33 | 34 | public static String md5Hex(String data) { 35 | if (data == null) { 36 | throw new IllegalArgumentException("data must not be null"); 37 | } 38 | 39 | byte[] bytes = digest("MD5", data); 40 | 41 | return toHexString(bytes); 42 | } 43 | 44 | public static String sha1Hex(String data) { 45 | if (data == null) { 46 | throw new IllegalArgumentException("data must not be null"); 47 | } 48 | 49 | byte[] bytes = digest("SHA1", data); 50 | 51 | return toHexString(bytes); 52 | } 53 | 54 | private static String toHexString(byte[] bytes) { 55 | int l = bytes.length; 56 | 57 | char[] out = new char[l << 1]; 58 | 59 | for (int i = 0, j = 0; i < l; i++) { 60 | out[j++] = DIGITS[(0xF0 & bytes[i]) >>> 4]; 61 | out[j++] = DIGITS[0x0F & bytes[i]]; 62 | } 63 | 64 | return new String(out); 65 | } 66 | 67 | private static byte[] digest(String algorithm, String data) { 68 | MessageDigest digest; 69 | try { 70 | digest = MessageDigest.getInstance(algorithm); 71 | } catch (NoSuchAlgorithmException e) { 72 | throw new RuntimeException(e); 73 | } 74 | 75 | return digest.digest(data.getBytes()); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/PropertiesHelperTest.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached 2 | 3 | import java.util.concurrent.TimeUnit 4 | 5 | class PropertiesHelperTest extends BaseTestCase { 6 | 7 | PropertiesHelper helper 8 | 9 | protected void setUp() { 10 | helper = newHelper() 11 | } 12 | 13 | 14 | 15 | void test_strings() { 16 | assertEquals "world", helper.get("hello") 17 | assertEquals "world", helper.get("hello", "blah") 18 | assertEquals "default", helper.get("nothing", "default") 19 | } 20 | 21 | void test_boolean() { 22 | assertFalse helper.getBoolean("blah", false) 23 | assertTrue helper.getBoolean("blah", true) 24 | assertTrue helper.getBoolean("thisIsTrue", false) 25 | assertFalse helper.getBoolean("thisIsFalse", true) 26 | 27 | //Boolean.parseBoolean returns false when it can't parse the value 28 | assertFalse helper.getBoolean("hello", true) 29 | } 30 | 31 | void test_long() { 32 | assertEquals 1L, helper.getLong("one", 10) 33 | assertEquals 10L, helper.getLong("nothing", 10) 34 | 35 | shouldFail(NumberFormatException) { 36 | helper.getLong("hello", 10) 37 | } 38 | } 39 | 40 | void test_int() { 41 | assertEquals 1, helper.getInt("one", 10) 42 | assertEquals 10, helper.getInt("nothing", 10) 43 | 44 | shouldFail(NumberFormatException) { 45 | helper.getInt("hello", 10) 46 | } 47 | } 48 | 49 | void test_enum() { 50 | assertEquals TimeUnit.SECONDS, helper.getEnum("seconds", TimeUnit, TimeUnit.NANOSECONDS) 51 | assertEquals TimeUnit.NANOSECONDS, helper.getEnum("nothing", TimeUnit, TimeUnit.NANOSECONDS) 52 | 53 | shouldFail(IllegalArgumentException) { 54 | helper.getEnum("hello", TimeUnit, TimeUnit.NANOSECONDS) 55 | } 56 | 57 | } 58 | 59 | void test_find_values() { 60 | assertNull helper.findValue("this", "does", "not", "exist") 61 | assertEquals "world", helper.findValue("this", "does", "not", "exist", "hello") 62 | } 63 | 64 | PropertiesHelper newHelper() { 65 | Properties props = new Properties() 66 | props.hello = "world" 67 | props.one = "1" 68 | props.thisIsTrue = "true" 69 | props.thisIsFalse = "false" 70 | props.seconds = "SECONDS" 71 | 72 | new PropertiesHelper(props) 73 | } 74 | 75 | } -------------------------------------------------------------------------------- /src/test/java/com/googlecode/hibernate/memcached/integration/AbstractHibernateTestCase.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached.integration; 2 | 3 | import com.googlecode.hibernate.memcached.BaseTestCase; 4 | import org.hibernate.Session; 5 | import org.hibernate.SessionFactory; 6 | import org.hibernate.Transaction; 7 | import org.hibernate.cfg.AnnotationConfiguration; 8 | import org.hibernate.cfg.Configuration; 9 | 10 | import java.util.Properties; 11 | 12 | /** 13 | * DOCUMENT ME! 14 | * 15 | * @author Ray Krueger 16 | */ 17 | public abstract class AbstractHibernateTestCase extends BaseTestCase { 18 | 19 | protected Session session; 20 | protected Transaction transaction; 21 | 22 | private Configuration getConfiguration() { 23 | AnnotationConfiguration config = new AnnotationConfiguration(); 24 | 25 | Properties properties = new Properties(); 26 | properties.putAll(getDefaultProperties()); 27 | properties.putAll(getConfigProperties()); 28 | 29 | config.setProperties(properties); 30 | config.addAnnotatedClass(Contact.class); 31 | 32 | 33 | return config; 34 | } 35 | 36 | private Properties getDefaultProperties() { 37 | Properties props = new Properties(); 38 | props.setProperty("hibernate.connection.driver_class", org.hsqldb.jdbcDriver.class.getName()); 39 | props.setProperty("hibernate.connection.url", "jdbc:hsqldb:mem:test"); 40 | props.setProperty("hibernate.connection.username", "sa"); 41 | props.setProperty("hibernate.connection.password", ""); 42 | props.setProperty("hibernate.cache.provider_class", 43 | com.googlecode.hibernate.memcached.MemcachedCacheProvider.class.getName()); 44 | props.setProperty("hibernate.hbm2ddl.auto", "create-drop"); 45 | return props; 46 | } 47 | 48 | protected Properties getConfigProperties() { 49 | return new Properties(); 50 | } 51 | 52 | void setupBeforeTransaction() { 53 | } 54 | 55 | @Override 56 | protected void setUp() { 57 | setupBeforeTransaction(); 58 | SessionFactory sessionFactory = getConfiguration().buildSessionFactory(); 59 | session = sessionFactory.openSession(); 60 | transaction = session.beginTransaction(); 61 | setupInTransaction(); 62 | } 63 | 64 | protected void setupInTransaction() { 65 | } 66 | 67 | protected void tearDownInTransaction() { 68 | } 69 | 70 | @Override 71 | protected void tearDown() { 72 | try { 73 | tearDownInTransaction(); 74 | } finally { 75 | transaction.rollback(); 76 | session.close(); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com/googlecode/hibernate/memcached/integration/ContactIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached.integration; 2 | 3 | import org.hibernate.Criteria; 4 | import static org.hibernate.criterion.Restrictions.eq; 5 | 6 | import java.util.Calendar; 7 | import java.util.Date; 8 | import java.util.Properties; 9 | 10 | public class ContactIntegrationTest extends AbstractHibernateTestCase { 11 | 12 | Contact ray; 13 | 14 | @Override 15 | protected void setupInTransaction() { 16 | 17 | ray = new Contact(); 18 | ray.setFirstName("Ray"); 19 | ray.setLastName("Krueger"); 20 | ray.setBirthday(new Date()); 21 | session.saveOrUpdate(ray); 22 | session.flush(); 23 | session.clear(); 24 | } 25 | 26 | @Override 27 | protected Properties getConfigProperties() { 28 | Properties props = new Properties(); 29 | props.setProperty("hibernate.cache.use_query_cache", "true"); 30 | return props; 31 | } 32 | 33 | public void test() { 34 | Contact fromDB = (Contact) session.get(Contact.class, ray.getId()); 35 | assertNotNull(fromDB); 36 | } 37 | 38 | public void test_query_cache() { 39 | Criteria criteria = session.createCriteria(Contact.class) 40 | .add(eq("firstName", "Ray")) 41 | .add(eq("lastName", "Krueger")) 42 | .setCacheable(true) 43 | .setCacheRegion("contact.findByFirstNameAndLastName"); 44 | 45 | assertNotNull(criteria.uniqueResult()); 46 | 47 | criteria.uniqueResult(); 48 | criteria.uniqueResult(); 49 | criteria.uniqueResult(); 50 | criteria.uniqueResult(); 51 | 52 | assertEquals(criteria.uniqueResult(), criteria.uniqueResult()); 53 | } 54 | 55 | public void test_query_cache_with_date() throws Exception { 56 | 57 | Thread.sleep(100); 58 | Calendar birthday = Calendar.getInstance(); 59 | birthday.set(Calendar.HOUR_OF_DAY, 0); 60 | birthday.set(Calendar.MINUTE, 0); 61 | birthday.set(Calendar.SECOND, 0); 62 | birthday.set(Calendar.MILLISECOND, 0); 63 | 64 | Criteria criteria = session.createCriteria(Contact.class) 65 | .add(eq("firstName", "Ray")) 66 | .add(eq("lastName", "Krueger")) 67 | .add(eq("birthday", birthday.getTime())) 68 | .setCacheable(true) 69 | .setCacheRegion("contact.findByFirstNameAndLastNameAndBirthday"); 70 | 71 | assertNotNull(criteria.uniqueResult()); 72 | criteria.uniqueResult(); 73 | criteria.uniqueResult(); 74 | criteria.uniqueResult(); 75 | 76 | assertEquals(criteria.uniqueResult(), criteria.uniqueResult()); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/spymemcached/SpyMemcacheClientFactoryTest.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached.spymemcached 2 | 3 | import com.googlecode.hibernate.memcached.BaseTestCase 4 | import com.googlecode.hibernate.memcached.Memcache 5 | import com.googlecode.hibernate.memcached.PropertiesHelper 6 | import net.spy.memcached.DefaultHashAlgorithm 7 | import net.spy.memcached.MemcachedClient 8 | import net.spy.memcached.auth.PlainCallbackHandler 9 | 10 | class SpyMemcacheClientFactoryTest extends BaseTestCase { 11 | 12 | private Properties properties = new Properties() 13 | private Memcache client 14 | private SpyMemcacheClientFactory factory = new SpyMemcacheClientFactory(new PropertiesHelper(properties)) 15 | 16 | void test_defaults() { 17 | client = factory.createMemcacheClient() 18 | assert client 19 | } 20 | 21 | void test_all_properties_set() { 22 | 23 | properties.setProperty "hibernate.memcached.servers", "localhost:11211 localhost:11212" 24 | properties.setProperty "hibernate.memcached.hashAlgorithm", DefaultHashAlgorithm.CRC_HASH.name() 25 | properties.setProperty "hibernate.memcached.operationQueueLength", "8192" 26 | properties.setProperty "hibernate.memcached.readBufferLength", "8192" 27 | properties.setProperty "hibernate.memcached.operationTimeout", "5000" 28 | properties.setProperty "hibernate.memcached.daemonMode", "true" 29 | 30 | client = factory.createMemcacheClient() 31 | assert client 32 | } 33 | 34 | void testNoAuth() { 35 | client = factory.createMemcacheClient() 36 | assertTrue client instanceof SpyMemcache 37 | SpyMemcache spyMemcache = client 38 | MemcachedClient memcachedClient = spyMemcache.memcachedClient 39 | assertNull memcachedClient.authDescriptor 40 | } 41 | 42 | void testAuth() { 43 | String username = 'user' 44 | String password = 'pass' 45 | 46 | properties.setProperty 'hibernate.memcached.username', username 47 | properties.setProperty 'hibernate.memcached.password', password 48 | 49 | client = factory.createMemcacheClient() 50 | assertTrue client instanceof SpyMemcache 51 | 52 | SpyMemcache spyMemcache = client 53 | MemcachedClient memcachedClient = spyMemcache.memcachedClient 54 | assertNotNull memcachedClient.authDescriptor 55 | 56 | assertTrue memcachedClient.authDescriptor.cbh instanceof PlainCallbackHandler 57 | PlainCallbackHandler cbh = memcachedClient.authDescriptor.cbh 58 | assertEquals username, cbh.username 59 | assertEquals password.toCharArray(), cbh.password 60 | } 61 | 62 | protected void tearDown() { 63 | client?.shutdown() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/spymemcached/SpyMemcache.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached.spymemcached; 2 | 3 | import com.googlecode.hibernate.memcached.LoggingMemcacheExceptionHandler; 4 | import com.googlecode.hibernate.memcached.Memcache; 5 | import com.googlecode.hibernate.memcached.MemcacheExceptionHandler; 6 | import com.googlecode.hibernate.memcached.utils.StringUtils; 7 | import net.spy.memcached.MemcachedClient; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.Map; 12 | 13 | /** 14 | * DOCUMENT ME! 15 | * 16 | * @author Ray Krueger 17 | */ 18 | public class SpyMemcache implements Memcache { 19 | 20 | private static final Logger log = LoggerFactory.getLogger(SpyMemcache.class); 21 | private MemcacheExceptionHandler exceptionHandler = new LoggingMemcacheExceptionHandler(); 22 | 23 | private final MemcachedClient memcachedClient; 24 | 25 | public SpyMemcache(MemcachedClient memcachedClient) { 26 | this.memcachedClient = memcachedClient; 27 | } 28 | 29 | public Object get(String key) { 30 | try { 31 | log.debug("MemcachedClient.get({})", key); 32 | return memcachedClient.get(key); 33 | } catch (Exception e) { 34 | exceptionHandler.handleErrorOnGet(key, e); 35 | } 36 | return null; 37 | } 38 | 39 | public Map getMulti(String... keys) { 40 | try { 41 | return memcachedClient.getBulk(keys); 42 | } catch (Exception e) { 43 | exceptionHandler.handleErrorOnGet(StringUtils.join(keys, ", "), e); 44 | } 45 | return null; 46 | } 47 | 48 | public void set(String key, int cacheTimeSeconds, Object o) { 49 | log.debug("MemcachedClient.set({})", key); 50 | try { 51 | memcachedClient.set(key, cacheTimeSeconds, o); 52 | } catch (Exception e) { 53 | exceptionHandler.handleErrorOnSet(key, cacheTimeSeconds, o, e); 54 | } 55 | } 56 | 57 | public void delete(String key) { 58 | try { 59 | memcachedClient.delete(key); 60 | } catch (Exception e) { 61 | exceptionHandler.handleErrorOnDelete(key, e); 62 | } 63 | } 64 | 65 | public void incr(String key, int factor, int startingValue) { 66 | try { 67 | memcachedClient.incr(key, factor, startingValue); 68 | } catch (Exception e) { 69 | exceptionHandler.handleErrorOnIncr(key, factor, startingValue, e); 70 | } 71 | } 72 | 73 | public void shutdown() { 74 | log.debug("Shutting down spy MemcachedClient"); 75 | memcachedClient.shutdown(); 76 | } 77 | 78 | public void setExceptionHandler(MemcacheExceptionHandler exceptionHandler) { 79 | this.exceptionHandler = exceptionHandler; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/groovy/com/googlecode/hibernate/memcached/ConfigTest.groovy: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached 2 | /** 3 | * DOCUMENT ME! 4 | * @author Ray Krueger 5 | */ 6 | class ConfigTest extends BaseTestCase { 7 | 8 | Config newConfig(Properties props) { 9 | new Config(new PropertiesHelper(props)) 10 | } 11 | 12 | void test_cache_time_seconds() { 13 | Properties p = new Properties() 14 | p["hibernate.memcached.cacheTimeSeconds"] = "10" 15 | p["hibernate.memcached.REGION.cacheTimeSeconds"] = "20" 16 | 17 | Config config = newConfig(p) 18 | assertEquals 10, config.getCacheTimeSeconds(null) 19 | assertEquals 20, config.getCacheTimeSeconds("REGION") 20 | } 21 | 22 | void test_clear_supported() { 23 | 24 | Properties p = new Properties() 25 | p["hibernate.memcached.clearSupported"] = "true" 26 | p["hibernate.memcached.REGION.clearSupported"] = "false" 27 | 28 | Config config = newConfig(p) 29 | assertTrue config.isClearSupported(null) 30 | assertFalse config.isClearSupported("REGION") 31 | } 32 | 33 | void test_key_strategy_name() { 34 | 35 | Properties p = new Properties() 36 | p["hibernate.memcached.keyStrategy"] = "batman" 37 | p["hibernate.memcached.REGION.keyStrategy"] = "robin" 38 | 39 | Config config = newConfig(p) 40 | assertEquals "batman", config.getKeyStrategyName(null) 41 | assertEquals "robin", config.getKeyStrategyName("REGION") 42 | } 43 | 44 | void test_dogpile_prevention() { 45 | 46 | Properties p = new Properties() 47 | p["hibernate.memcached.dogpilePrevention"] = "true" 48 | p["hibernate.memcached.REGION.dogpilePrevention"] = "false" 49 | 50 | Config config = newConfig(p) 51 | assertTrue config.isDogpilePreventionEnabled(null) 52 | assertFalse config.isDogpilePreventionEnabled("REGION") 53 | } 54 | 55 | void test_dogpile_prevention_expiration_factor() { 56 | Properties p = new Properties() 57 | p["hibernate.memcached.dogpilePrevention.expirationFactor"] = "10" 58 | p["hibernate.memcached.REGION.dogpilePrevention.expirationFactor"] = "20" 59 | 60 | Config config = newConfig(p) 61 | assertEquals 10, config.getDogpilePreventionExpirationFactor(null) 62 | assertEquals 20, config.getDogpilePreventionExpirationFactor("REGION") 63 | } 64 | 65 | void test_memcache_client_factory_name() { 66 | 67 | Properties p = new Properties() 68 | Config config = newConfig(p) 69 | //test default 70 | assertEquals "com.googlecode.hibernate.memcached.spymemcached.SpyMemcacheClientFactory", 71 | config.getMemcachedClientFactoryName() 72 | 73 | p["hibernate.memcached.memcacheClientFactory"] = "blah" 74 | assertEquals "blah", config.getMemcachedClientFactoryName() 75 | 76 | 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hibernate-memcached 2 | A library for using Memcached as a second level distributed cache in Hibernate. 3 | 4 | * Based on the excellent spymemcached client 5 | * Includes support for the Whalin (danga) memcached client 6 | * Supports entity and query caching. 7 | 8 | # Help 9 | If you have any questions, or just want to drop a line to say it's working great :) use the [google-group](http://groups.google.com/group/hibernate-memcached). 10 | 11 | Please note that this is an open source project. I work on it when I can and I implement what I feel like. I am volunteering my own free time for my own amusement. 12 | 13 | # Versions 14 | ## 1.3 15 | * [HashCodeKeyStrategy][1] [StringKeyStrategy][2] are now both deprecated. 16 | * [Sha1KeyStrategy][3] is now the default strategy. 17 | * [Md5KeyStrategy][4] and [Sha1KeyStrategy][3] both digest the entire combined key now (region, clear index, key) 18 | * [Md5KeyStrategy][4] and [Sha1KeyStrategy][3] have been re-implemented to hash both the toString() and hashCode() values 19 | of the Hibernate query key object. This should eliminate collisions when using hashCode() alone. 20 | [Issue 22](http://code.google.com/p/hibernate-memcached/issues/detail?id=22). 21 | * [HashCodeKeyStrategy][1] [StringKeyStrategy][2] will throw exceptions now if the key length is greater than 250. 22 | 23 | As a result of these changes hibernate-memcached will miss on all cache requests upon upgrading to this version. This 24 | is due to the switch to Sha1KeyStrategy as the default. Hibernate-memcached will now generate different keys for the 25 | same data you were caching previously. Essentially, your cache will appear empty to Hibernate. 26 | 27 | Also, as a result of these changes, 1.3 may not be binary compatible with any subclass hacks you may have written that 28 | extend HashCodeKeyStrategy, StringKeyStrategy, Md5KeyStrategy, or Sha1KeyStrategy. Note that the KeyStrategy interface 29 | and AbstractKeyStrategy have not changed at all. If you implemented/extended those directly you're fine. 30 | 31 | * Patch from @burtbeckwith to allow for memcached authentication via the spymemcached client. 32 | This can be specified using "hibernate.memcached.username" and "hibernate.memcached.password" 33 | 34 | ## 1.2.2 35 | * Patch from ddlatham to allow the spymemcached library to be put 36 | into daemon mode. This is accomplished by setting 37 | hibernate.memcached.daemonMode to true. 38 | * Updated the maven pom to pull in spymemcached 2.4.2 by default. 39 | 40 | # Note on Patches/Pull Requests 41 | 42 | * Fork the project. 43 | * Make your feature addition or bug fix. 44 | * Add tests for it. This is important so I don't break it in a future version unintentionally. 45 | * Commit, do not mess with pom.xml, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull) 46 | * Send me a pull request. Bonus points for topic branches. 47 | 48 | [1]: hibernate-memcached/blob/master/src/main/java/com/googlecode/hibernate/memcached/HashCodeKeyStrategy.java 49 | [2]: hibernate-memcached/blob/master/src/main/java/com/googlecode/hibernate/memcached/StringKeyStrategy.java 50 | [3]: hibernate-memcached/blob/master/src/main/java/com/googlecode/hibernate/memcached/Sha1KeyStrategy.java 51 | [4]: hibernate-memcached/blob/master/src/main/java/com/googlecode/hibernate/memcached/Md5KeyStrategy.java 52 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/dangamemcached/DangaMemcache.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached.dangamemcached; 2 | 3 | import com.danga.MemCached.MemCachedClient; 4 | import com.danga.MemCached.SockIOPool; 5 | import com.googlecode.hibernate.memcached.LoggingMemcacheExceptionHandler; 6 | import com.googlecode.hibernate.memcached.Memcache; 7 | import com.googlecode.hibernate.memcached.MemcacheExceptionHandler; 8 | import com.googlecode.hibernate.memcached.utils.StringUtils; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.Calendar; 13 | import java.util.Date; 14 | import java.util.Map; 15 | 16 | /** 17 | * DOCUMENT ME! 18 | * 19 | * @author George Wei 20 | */ 21 | public class DangaMemcache implements Memcache { 22 | 23 | private static final Logger log = LoggerFactory.getLogger(DangaMemcache.class); 24 | 25 | private final MemCachedClient memcachedClient; 26 | private final String poolName; 27 | 28 | private MemcacheExceptionHandler exceptionHandler = new LoggingMemcacheExceptionHandler(); 29 | 30 | /* Constructor 31 | * 32 | * @param memcachedClient Instance of Danga's MemCachedClient 33 | * @param poolName SockIOPool name used to instantiate memcachedClient 34 | */ 35 | public DangaMemcache(MemCachedClient memcachedClient, String poolName) { 36 | this.memcachedClient = memcachedClient; 37 | this.poolName = poolName; 38 | } 39 | 40 | public Object get(String key) { 41 | try { 42 | log.debug("MemCachedClient.get({})", key); 43 | return memcachedClient.get(key); 44 | } catch (Exception e) { 45 | exceptionHandler.handleErrorOnGet(key, e); 46 | } 47 | return null; 48 | } 49 | 50 | public Map getMulti(String... keys) { 51 | try { 52 | return memcachedClient.getMulti(keys); 53 | } catch (Exception e) { 54 | exceptionHandler.handleErrorOnGet(StringUtils.join(keys, ", "), e); 55 | } 56 | return null; 57 | } 58 | 59 | public void set(String key, int cacheTimeSeconds, Object o) { 60 | log.debug("MemCachedClient.set({})", key); 61 | try { 62 | Calendar calendar = Calendar.getInstance(); 63 | calendar.setTime(new Date()); 64 | calendar.add(Calendar.SECOND, cacheTimeSeconds); 65 | 66 | memcachedClient.set(key, o, calendar.getTime()); 67 | } catch (Exception e) { 68 | exceptionHandler.handleErrorOnSet(key, cacheTimeSeconds, o, e); 69 | } 70 | } 71 | 72 | public void delete(String key) { 73 | try { 74 | memcachedClient.delete(key); 75 | } catch (Exception e) { 76 | exceptionHandler.handleErrorOnDelete(key, e); 77 | } 78 | } 79 | 80 | public void incr(String key, int factor, int startingValue) { 81 | try { 82 | //Try to incr 83 | long rv = memcachedClient.incr(key, factor); 84 | 85 | //If the key is not found, add it with startingValue 86 | if (-1 == rv) 87 | memcachedClient.addOrIncr(key, startingValue); 88 | } catch (Exception e) { 89 | exceptionHandler.handleErrorOnIncr(key, factor, startingValue, e); 90 | } 91 | } 92 | 93 | public void shutdown() { 94 | log.debug("Shutting down danga MemCachedClient"); 95 | 96 | //Danga's MemCachedClient does not provide a method to shutdown or 97 | //close it, let's shutdown its SockIOPool instead 98 | SockIOPool.getInstance(poolName).shutDown(); 99 | } 100 | 101 | public void setExceptionHandler(MemcacheExceptionHandler exceptionHandler) { 102 | this.exceptionHandler = exceptionHandler; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/Config.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached; 2 | 3 | /** 4 | * DOCUMENT ME! 5 | * 6 | * @author Ray Krueger 7 | */ 8 | public class Config { 9 | 10 | public static final String PROP_PREFIX = "hibernate.memcached."; 11 | 12 | private static final String CACHE_TIME_SECONDS = "cacheTimeSeconds"; 13 | public static final String PROP_CACHE_TIME_SECONDS = PROP_PREFIX + CACHE_TIME_SECONDS; 14 | 15 | private static final String CLEAR_SUPPORTED = "clearSupported"; 16 | public static final String PROP_CLEAR_SUPPORTED = PROP_PREFIX + CLEAR_SUPPORTED; 17 | 18 | private static final String MEMCACHE_CLIENT_FACTORY = "memcacheClientFactory"; 19 | public static final String PROP_MEMCACHE_CLIENT_FACTORY = PROP_PREFIX + MEMCACHE_CLIENT_FACTORY; 20 | 21 | private static final String DOGPILE_PREVENTION = "dogpilePrevention"; 22 | public static final String PROP_DOGPILE_PREVENTION = PROP_PREFIX + DOGPILE_PREVENTION; 23 | 24 | private static final String DOGPILE_PREVENTION_EXPIRATION_FACTOR = "dogpilePrevention.expirationFactor"; 25 | public static final String PROP_DOGPILE_PREVENTION_EXPIRATION_FACTOR = PROP_PREFIX + DOGPILE_PREVENTION_EXPIRATION_FACTOR; 26 | 27 | private static final String KEY_STRATEGY = "keyStrategy"; 28 | 29 | public static final int DEFAULT_CACHE_TIME_SECONDS = 300; 30 | public static final boolean DEFAULT_CLEAR_SUPPORTED = false; 31 | public static final boolean DEFAULT_DOGPILE_PREVENTION = false; 32 | public static final String DEFAULT_MEMCACHE_CLIENT_FACTORY = "com.googlecode.hibernate.memcached.spymemcached.SpyMemcacheClientFactory"; 33 | 34 | private PropertiesHelper props; 35 | private static final int DEFAULT_DOGPILE_EXPIRATION_FACTOR = 2; 36 | 37 | public Config(PropertiesHelper props) { 38 | this.props = props; 39 | } 40 | 41 | public int getCacheTimeSeconds(String cacheRegion) { 42 | int globalCacheTimeSeconds = props.getInt(PROP_CACHE_TIME_SECONDS, 43 | DEFAULT_CACHE_TIME_SECONDS); 44 | return props.getInt(cacheRegionPrefix(cacheRegion) + CACHE_TIME_SECONDS, 45 | globalCacheTimeSeconds); 46 | } 47 | 48 | public String getKeyStrategyName(String cacheRegion) { 49 | String globalKeyStrategy = props.get(PROP_PREFIX + KEY_STRATEGY, 50 | Sha1KeyStrategy.class.getName()); 51 | return props.get(cacheRegionPrefix(cacheRegion) + KEY_STRATEGY, globalKeyStrategy); 52 | } 53 | 54 | public boolean isClearSupported(String cacheRegion) { 55 | boolean globalClearSupported = props.getBoolean(PROP_CLEAR_SUPPORTED, 56 | DEFAULT_CLEAR_SUPPORTED); 57 | return props.getBoolean(cacheRegionPrefix(cacheRegion) + CLEAR_SUPPORTED, 58 | globalClearSupported); 59 | } 60 | 61 | public boolean isDogpilePreventionEnabled(String cacheRegion) { 62 | boolean globalDogpilePrevention = props.getBoolean(PROP_DOGPILE_PREVENTION, 63 | DEFAULT_DOGPILE_PREVENTION); 64 | return props.getBoolean(cacheRegionPrefix(cacheRegion) + DOGPILE_PREVENTION, 65 | globalDogpilePrevention); 66 | } 67 | 68 | public double getDogpilePreventionExpirationFactor(String cacheRegion) { 69 | double globalFactor = props.getDouble(PROP_DOGPILE_PREVENTION_EXPIRATION_FACTOR, 70 | DEFAULT_DOGPILE_EXPIRATION_FACTOR); 71 | return props.getDouble(cacheRegionPrefix(cacheRegion) + DOGPILE_PREVENTION_EXPIRATION_FACTOR, 72 | globalFactor); 73 | } 74 | 75 | public String getMemcachedClientFactoryName() { 76 | return props.get(PROP_MEMCACHE_CLIENT_FACTORY, 77 | DEFAULT_MEMCACHE_CLIENT_FACTORY); 78 | } 79 | 80 | private String cacheRegionPrefix(String cacheRegion) { 81 | return PROP_PREFIX + cacheRegion + "."; 82 | } 83 | 84 | public PropertiesHelper getPropertiesHelper() { 85 | return props; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/spymemcached/SpyMemcacheClientFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached.spymemcached; 2 | 3 | import net.spy.memcached.*; 4 | import net.spy.memcached.auth.AuthDescriptor; 5 | import net.spy.memcached.auth.PlainCallbackHandler; 6 | 7 | import com.googlecode.hibernate.memcached.Config; 8 | import com.googlecode.hibernate.memcached.Memcache; 9 | import com.googlecode.hibernate.memcached.MemcacheClientFactory; 10 | import com.googlecode.hibernate.memcached.PropertiesHelper; 11 | 12 | /** 13 | * Parses hibernate properties to produce a MemcachedClient.
14 | * See {@link com.googlecode.hibernate.memcached.MemcachedCacheProvider} for property details. 15 | * 16 | * @author Ray Krueger 17 | */ 18 | public class SpyMemcacheClientFactory implements MemcacheClientFactory { 19 | 20 | public static final String PROP_SERVERS = Config.PROP_PREFIX + "servers"; 21 | public static final String PROP_OPERATION_QUEUE_LENGTH = Config.PROP_PREFIX + "operationQueueLength"; 22 | public static final String PROP_READ_BUFFER_SIZE = Config.PROP_PREFIX + "readBufferSize"; 23 | public static final String PROP_OPERATION_TIMEOUT = Config.PROP_PREFIX + "operationTimeout"; 24 | public static final String PROP_HASH_ALGORITHM = Config.PROP_PREFIX + "hashAlgorithm"; 25 | public static final String PROP_CONNECTION_FACTORY = Config.PROP_PREFIX + "connectionFactory"; 26 | public static final String PROP_DAEMON_MODE = Config.PROP_PREFIX + "daemonMode"; 27 | public static final String PROP_USERNAME = Config.PROP_PREFIX + "username"; 28 | public static final String PROP_PASSWORD = Config.PROP_PREFIX + "password"; 29 | private final PropertiesHelper properties; 30 | 31 | public SpyMemcacheClientFactory(PropertiesHelper properties) { 32 | this.properties = properties; 33 | } 34 | 35 | public Memcache createMemcacheClient() throws Exception { 36 | 37 | ConnectionFactory connectionFactory = getConnectionFactory(); 38 | 39 | MemcachedClient client = new MemcachedClient(connectionFactory, AddrUtil.getAddresses(getServerList())); 40 | return new SpyMemcache(client); 41 | } 42 | 43 | protected ConnectionFactory getConnectionFactory() { 44 | 45 | if (connectionFactoryNameEquals(DefaultConnectionFactory.class)) { 46 | return buildDefaultConnectionFactory(); 47 | } 48 | 49 | if (connectionFactoryNameEquals(KetamaConnectionFactory.class)) { 50 | return buildKetamaConnectionFactory(); 51 | } 52 | 53 | if (connectionFactoryNameEquals(BinaryConnectionFactory.class)) { 54 | return buildBinaryConnectionFactory(); 55 | } 56 | 57 | throw new IllegalArgumentException("Unsupported " + PROP_CONNECTION_FACTORY + " value: " + getConnectionFactoryName()); 58 | } 59 | 60 | private boolean connectionFactoryNameEquals(Class cls) { 61 | return cls.getSimpleName().equals(getConnectionFactoryName()); 62 | } 63 | 64 | private DefaultConnectionFactory buildDefaultConnectionFactory() { 65 | return new DefaultConnectionFactory(getOperationQueueLength(), getReadBufferSize(), getHashAlgorithm()) { 66 | @Override 67 | public long getOperationTimeout() { 68 | return getOperationTimeoutMillis(); 69 | } 70 | 71 | @Override 72 | public boolean isDaemon() { 73 | return isDaemonMode(); 74 | } 75 | 76 | @Override 77 | public AuthDescriptor getAuthDescriptor() { 78 | return createAuthDescriptor(); 79 | } 80 | }; 81 | } 82 | 83 | private KetamaConnectionFactory buildKetamaConnectionFactory() { 84 | return new KetamaConnectionFactory() { 85 | @Override 86 | public long getOperationTimeout() { 87 | return getOperationTimeoutMillis(); 88 | } 89 | 90 | @Override 91 | public boolean isDaemon() { 92 | return isDaemonMode(); 93 | } 94 | 95 | @Override 96 | public AuthDescriptor getAuthDescriptor() { 97 | return createAuthDescriptor(); 98 | } 99 | }; 100 | } 101 | 102 | private BinaryConnectionFactory buildBinaryConnectionFactory() { 103 | return new BinaryConnectionFactory(getOperationQueueLength(), getReadBufferSize(), getHashAlgorithm()) { 104 | @Override 105 | public long getOperationTimeout() { 106 | return getOperationTimeoutMillis(); 107 | } 108 | 109 | @Override 110 | public boolean isDaemon() { 111 | return isDaemonMode(); 112 | } 113 | 114 | @Override 115 | public AuthDescriptor getAuthDescriptor() { 116 | return createAuthDescriptor(); 117 | } 118 | }; 119 | } 120 | 121 | protected AuthDescriptor createAuthDescriptor() { 122 | String username = properties.get(PROP_USERNAME); 123 | String password = properties.get(PROP_PASSWORD); 124 | if (username == null || password == null) { 125 | return null; 126 | } 127 | return new AuthDescriptor(new String[] { "PLAIN" }, 128 | new PlainCallbackHandler(username, password)); 129 | } 130 | 131 | public String getServerList() { 132 | return properties.get(PROP_SERVERS, "localhost:11211"); 133 | } 134 | 135 | public int getOperationQueueLength() { 136 | return properties.getInt(PROP_OPERATION_QUEUE_LENGTH, 137 | DefaultConnectionFactory.DEFAULT_OP_QUEUE_LEN); 138 | } 139 | 140 | public int getReadBufferSize() { 141 | return properties.getInt(PROP_READ_BUFFER_SIZE, 142 | DefaultConnectionFactory.DEFAULT_READ_BUFFER_SIZE); 143 | } 144 | 145 | public long getOperationTimeoutMillis() { 146 | return properties.getLong(PROP_OPERATION_TIMEOUT, 147 | DefaultConnectionFactory.DEFAULT_OPERATION_TIMEOUT); 148 | } 149 | 150 | public boolean isDaemonMode() { 151 | return properties.getBoolean(PROP_DAEMON_MODE, false); 152 | } 153 | 154 | public HashAlgorithm getHashAlgorithm() { 155 | return properties.getEnum(PROP_HASH_ALGORITHM, 156 | DefaultHashAlgorithm.class, 157 | DefaultHashAlgorithm.NATIVE_HASH); 158 | } 159 | 160 | public String getConnectionFactoryName() { 161 | return properties.get(PROP_CONNECTION_FACTORY, 162 | DefaultConnectionFactory.class.getSimpleName()); 163 | } 164 | 165 | protected PropertiesHelper getProperties() { 166 | return properties; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/dangamemcached/DangaMemcacheClientFactory.java: -------------------------------------------------------------------------------- 1 | package com.googlecode.hibernate.memcached.dangamemcached; 2 | 3 | import com.danga.MemCached.ErrorHandler; 4 | import com.danga.MemCached.MemCachedClient; 5 | import com.danga.MemCached.SockIOPool; 6 | import com.googlecode.hibernate.memcached.Memcache; 7 | import com.googlecode.hibernate.memcached.MemcacheClientFactory; 8 | import com.googlecode.hibernate.memcached.PropertiesHelper; 9 | import org.hibernate.cache.CacheException; 10 | 11 | /** 12 | * DOCUMENT ME! 13 | * 14 | * @author George Wei 15 | */ 16 | public class DangaMemcacheClientFactory implements MemcacheClientFactory { 17 | 18 | public static final String PROP_PREFIX = "hibernate.memcached."; 19 | 20 | public static final String PROP_COMPRESS_ENABLE = PROP_PREFIX + "compressEnable"; 21 | public static final String PROP_DEFAULT_ENCODING = PROP_PREFIX + "defaultEncoding"; 22 | public static final String PROP_POOL_NAME = PROP_PREFIX + "poolName"; 23 | public static final String PROP_ERROR_HANDLER = PROP_PREFIX + "errorHandler"; 24 | public static final String PROP_SERVERS = PROP_PREFIX + "servers"; 25 | public static final String PROP_WEIGHTS = PROP_PREFIX + "weights"; 26 | public static final String PROP_INIT_CONN = PROP_PREFIX + "initConn"; 27 | public static final String PROP_MIN_CONN = PROP_PREFIX + "minConn"; 28 | public static final String PROP_MAX_CONN = PROP_PREFIX + "maxConn"; 29 | public static final String PROP_MAX_IDLE = PROP_PREFIX + "maxIdle"; 30 | public static final String PROP_MAINT_SLEEP = PROP_PREFIX + "maintSleep"; 31 | public static final String PROP_SOCKET_TIMEOUT = PROP_PREFIX + "socketTimeout"; 32 | public static final String PROP_SOCKET_CONNECT_TIMEOUT = PROP_PREFIX + "socketConnectTimeout"; 33 | 34 | public static final boolean DEFAULT_COMPRESS_ENABLE = true; 35 | public static final String DEFAULT_DEFAULT_ENCODING = "UTF-8"; 36 | public static final String DEFAULT_POOL_NAME = "default"; 37 | public static final String DEFAULT_ERROR_HANDLER = "com.googlecode.hibernate.memcached.dangamemcached.SimpleErrorHandler"; 38 | public static final String DEFAULT_SERVERS = "localhost:11211"; 39 | public static final int DEFAULT_INIT_CONN = 1; 40 | public static final int DEFAULT_MIN_CONN = 1; 41 | public static final int DEFAULT_MAX_CONN = 10; 42 | public static final int DEFAULT_MAX_IDLE = 1000 * 60 * 5; //5 minutes 43 | public static final int DEFAULT_MAINT_SLEEP = 1000 * 30; //30 seconds 44 | public static final int DEFAULT_SOCKET_TIMEOUT = 1000 * 30; //30 seconds 45 | public static final int DEFAULT_SOCKET_CONNECT_TIMEOUT = 1000 * 3; //3 seconds 46 | 47 | private PropertiesHelper properties; 48 | 49 | public DangaMemcacheClientFactory(PropertiesHelper properties) { 50 | this.properties = properties; 51 | } 52 | 53 | public Memcache createMemcacheClient() throws Exception { 54 | String poolName = getPoolName(); 55 | 56 | // grab an instance of our connection pool 57 | SockIOPool pool = SockIOPool.getInstance(poolName); 58 | 59 | // set the servers and the weights 60 | pool.setServers(getServers()); 61 | pool.setWeights(getWeights()); 62 | 63 | // set some basic pool settings 64 | pool.setInitConn(getInitConn()); 65 | pool.setMinConn(getMinConn()); 66 | pool.setMaxConn(getMaxConn()); 67 | pool.setMaxIdle(getMaxIdle()); 68 | 69 | // set the sleep for the maint thread 70 | // it will wake up every x seconds and 71 | // maintain the pool size 72 | pool.setMaintSleep(getMaintSleep()); 73 | 74 | // set some TCP settings 75 | pool.setNagle(false); 76 | pool.setSocketTO(getSocketTimeout()); 77 | pool.setSocketConnectTO(getSocketConnectTimeout()); 78 | 79 | // initialize the connection pool 80 | pool.initialize(); 81 | 82 | MemCachedClient client = 83 | new MemCachedClient( 84 | getClassLoader(), 85 | getErrorHandler(), 86 | poolName); 87 | client.setCompressEnable(isCompressEnable()); 88 | client.setDefaultEncoding(getDefaultEncoding()); 89 | 90 | return new DangaMemcache(client, poolName); 91 | } 92 | 93 | public ClassLoader getClassLoader() { 94 | return Thread.currentThread().getContextClassLoader(); 95 | } 96 | 97 | public boolean isCompressEnable() { 98 | return properties.getBoolean(PROP_COMPRESS_ENABLE, DEFAULT_COMPRESS_ENABLE); 99 | } 100 | 101 | public String getDefaultEncoding() { 102 | return properties.get(PROP_DEFAULT_ENCODING, DEFAULT_DEFAULT_ENCODING); 103 | } 104 | 105 | public String getPoolName() { 106 | return properties.get(PROP_POOL_NAME, DEFAULT_POOL_NAME); 107 | } 108 | 109 | public ErrorHandler getErrorHandler() { 110 | String errorHandlerName = 111 | properties.get(PROP_ERROR_HANDLER, DEFAULT_ERROR_HANDLER); 112 | 113 | ErrorHandler errorHandler; 114 | try { 115 | errorHandler = 116 | (ErrorHandler) Class.forName(errorHandlerName).newInstance(); 117 | } catch (ClassNotFoundException e) { 118 | throw new CacheException( 119 | "Unable to find error handler class [" + errorHandlerName + "]", e); 120 | } catch (IllegalAccessException e) { 121 | throw new CacheException( 122 | "Illegally accessed error handler class [" + errorHandlerName + "]", e); 123 | } catch (InstantiationException e) { 124 | throw new CacheException( 125 | "Failed to instantiate error handler class [" + errorHandlerName + "]", e); 126 | } 127 | 128 | return errorHandler; 129 | } 130 | 131 | public String[] getServers() { 132 | return properties.get(PROP_SERVERS, DEFAULT_SERVERS).split(" "); 133 | } 134 | 135 | public Integer[] getWeights() { 136 | String[] servers = getServers(); 137 | Integer[] weights = new Integer[servers.length]; 138 | String weightsValue = properties.get(PROP_WEIGHTS); 139 | 140 | if (weightsValue == null || "".equals(weightsValue)) { 141 | for (int i = 0; i < weights.length; i++) 142 | weights[i] = 1; 143 | } else { 144 | String[] weightsStrings = weightsValue.split(" "); 145 | if (weightsStrings.length == servers.length) { 146 | for (int i = 0; i < weights.length; i++) 147 | weights[i] = new Integer(weightsStrings[i]); 148 | } else 149 | throw new CacheException( 150 | "Count of weight number mismatch count of server"); 151 | } 152 | 153 | return weights; 154 | } 155 | 156 | public int getInitConn() { 157 | return properties.getInt(PROP_INIT_CONN, DEFAULT_INIT_CONN); 158 | } 159 | 160 | public int getMinConn() { 161 | return properties.getInt(PROP_MIN_CONN, DEFAULT_MIN_CONN); 162 | } 163 | 164 | public int getMaxConn() { 165 | return properties.getInt(PROP_MAX_CONN, DEFAULT_MAX_CONN); 166 | } 167 | 168 | public int getMaxIdle() { 169 | return properties.getInt(PROP_MAX_IDLE, DEFAULT_MAX_IDLE); 170 | } 171 | 172 | public int getMaintSleep() { 173 | return properties.getInt(PROP_MAINT_SLEEP, DEFAULT_MAINT_SLEEP); 174 | } 175 | 176 | public int getSocketTimeout() { 177 | return properties.getInt(PROP_SOCKET_TIMEOUT, DEFAULT_SOCKET_TIMEOUT); 178 | } 179 | 180 | public int getSocketConnectTimeout() { 181 | return properties.getInt(PROP_SOCKET_CONNECT_TIMEOUT, DEFAULT_SOCKET_CONNECT_TIMEOUT); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/MemcachedCacheProvider.java: -------------------------------------------------------------------------------- 1 | /* Copyright 2008 Ray Krueger 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.googlecode.hibernate.memcached; 16 | 17 | import org.hibernate.cache.Cache; 18 | import org.hibernate.cache.CacheException; 19 | import org.hibernate.cache.CacheProvider; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import java.lang.reflect.Constructor; 24 | import java.util.Properties; 25 | 26 | /** 27 | * Configures an instance of {@link MemcachedCache} for use as a second-level cache in Hibernate. 28 | * To use set the hibernate property hibernate.cache.provider_class to the name of this class. 29 | *

30 | * There are two types of property settings that the MemcachedCacheProvider supports, cache-wide properties 31 | * and region-name properties. 32 | *

33 | * Cache wide properties 34 | * 35 | * 36 | * 37 | * 38 | * 39 | * 40 | * 41 | * 43 | * 44 | * 45 | * 46 | * 47 | * 48 | * 49 | * 50 | * 51 | * 52 | * 53 | * 57 | * 58 | * 61 | *
PropertyDefaultDescription
hibernate.memcached.serverslocalhost:11211Space delimited list of memcached instances in host:port format
hibernate.memcached.cacheTimeSeconds300The default number of seconds items should be cached. Can be overriden at the regon level.
hibernate.memcached.keyStrategy{@link Sha1KeyStrategy}Sets the strategy class to to use for generating cache keys. 42 | * Must provide a class name that implements {@link com.googlecode.hibernate.memcached.KeyStrategy}
hibernate.memcached.readBufferSize{@link net.spy.memcached.DefaultConnectionFactory#DEFAULT_READ_BUFFER_SIZE}The read buffer size for each server connection from this factory
hibernate.memcached.operationQueueLength{@link net.spy.memcached.DefaultConnectionFactory#DEFAULT_OP_QUEUE_LEN}Maximum length of the operation queue returned by this connection factory
hibernate.memcached.operationTimeout{@link net.spy.memcached.DefaultConnectionFactory#DEFAULT_OPERATION_TIMEOUT}Default operation timeout in milliseconds
hibernate.memcached.hashAlgorithm{@link net.spy.memcached.HashAlgorithm#KETAMA_HASH}Which hash algorithm to use when adding items to the cache.
54 | * Note: the MemcachedClient defaults to using 55 | * {@link net.spy.memcached.HashAlgorithm#NATIVE_HASH}, while the hibernate-memcached cache defaults to KETAMA_HASH 56 | * for "consistent hashing"
hibernate.memcached.clearSupportedfalseEnables support for the {@link MemcachedCache#clear()} method for all cache regions. 59 | * The way clear is implemented for memcached is expensive and adds overhead to all get/set operations. 60 | * It is not recommended for production use.
62 | *

63 | * Cache Region properties
64 | * Cache regon properties are set by giving your cached data a "region name" in hibernate. 65 | * You can tune the MemcachedCache instance for your region using the following properties. 66 | * These properties essentially override the cache-wide properties above.
67 | * 68 | * 69 | * 70 | * 71 | * 72 | * 73 | * 75 | * 76 | * 77 | * 79 | * 80 | *
PropertyDefaultDescription
hibernate.memcached.[region-name].cacheTimeSecondsnone, see hibernate.memcached.cacheTimeSecondsSet the cache time for this cache region, overriding the cache-wide setting.
hibernate.memcached.[region-name].keyStrategynone, see hibernate.memcached.keyStrategyOverrides the strategy class to to use for generating cache keys in this cache region. 74 | * Must provide a class name that implements {@link com.googlecode.hibernate.memcached.KeyStrategy}
hibernate.memcached.[region-name].clearSupportednone, see hibernate.memcached.clearSupportedEnables clear() operations for this cache region only. 78 | * Again, the clear operation incurs cost on every get/set operation.
81 | * 82 | * @author Ray Krueger 83 | */ 84 | public class MemcachedCacheProvider implements CacheProvider { 85 | 86 | private final Logger log = LoggerFactory.getLogger(MemcachedCacheProvider.class); 87 | 88 | private Memcache client; 89 | 90 | public Cache buildCache(String regionName, Properties properties) throws CacheException { 91 | 92 | Config config = new Config(new PropertiesHelper(properties)); 93 | 94 | log.info("Building cache for region [{}]", regionName); 95 | 96 | MemcachedCache cache = new MemcachedCache(regionName, client); 97 | 98 | String keyStrategy = config.getKeyStrategyName(regionName); 99 | if (keyStrategy != null) { 100 | setKeyStrategy(keyStrategy, cache); 101 | } 102 | 103 | cache.setCacheTimeSeconds( 104 | config.getCacheTimeSeconds(regionName) 105 | ); 106 | 107 | cache.setClearSupported( 108 | config.isClearSupported(regionName) 109 | ); 110 | 111 | boolean dogpilePrevention = config.isDogpilePreventionEnabled(regionName); 112 | cache.setDogpilePreventionEnabled(dogpilePrevention); 113 | 114 | if (dogpilePrevention) { 115 | cache.setDogpilePreventionExpirationFactor( 116 | config.getDogpilePreventionExpirationFactor(regionName) 117 | ); 118 | } 119 | 120 | return cache; 121 | } 122 | 123 | private void setKeyStrategy(String keyStrategyName, MemcachedCache cache) { 124 | log.debug("Using KeyStrategy: [{}]", keyStrategyName); 125 | KeyStrategy keyStrategy = instantiateKeyStrategy(keyStrategyName); 126 | cache.setKeyStrategy(keyStrategy); 127 | } 128 | 129 | protected KeyStrategy instantiateKeyStrategy(String cls) { 130 | try { 131 | return (KeyStrategy) Class.forName(cls).newInstance(); 132 | } catch (InstantiationException e) { 133 | throw new CacheException("Could not instantiate keyStrategy class", e); 134 | } catch (IllegalAccessException e) { 135 | throw new CacheException("Could not instantiate keyStrategy class", e); 136 | } catch (ClassNotFoundException e) { 137 | throw new CacheException("Could not instantiate keyStrategy class", e); 138 | } 139 | } 140 | 141 | /** 142 | * No clue what this is for, Hibernate docs don't say. 143 | * 144 | * @return long {@link org.hibernate.cache.Timestamper#next()} 145 | */ 146 | public long nextTimestamp() { 147 | return System.currentTimeMillis() / 100; 148 | } 149 | 150 | public void start(Properties properties) throws CacheException { 151 | log.info("Starting MemcachedClient..."); 152 | try { 153 | client = getMemcachedClientFactory(new Config(new PropertiesHelper(properties))) 154 | .createMemcacheClient(); 155 | } catch (Exception e) { 156 | throw new CacheException("Unable to initialize MemcachedClient", e); 157 | } 158 | } 159 | 160 | protected MemcacheClientFactory getMemcachedClientFactory(Config config) { 161 | String factoryClassName = config.getMemcachedClientFactoryName(); 162 | 163 | Constructor constructor; 164 | try { 165 | constructor = Class.forName(factoryClassName) 166 | .getConstructor(PropertiesHelper.class); 167 | } catch (ClassNotFoundException e) { 168 | throw new CacheException( 169 | "Unable to find factory class [" + factoryClassName + "]", e); 170 | } catch (NoSuchMethodException e) { 171 | throw new CacheException( 172 | "Unable to find PropertiesHelper constructor for factory class [" + factoryClassName + "]", e); 173 | } 174 | 175 | MemcacheClientFactory clientFactory; 176 | try { 177 | clientFactory = (MemcacheClientFactory) constructor.newInstance(config.getPropertiesHelper()); 178 | } catch (Exception e) { 179 | throw new CacheException( 180 | "Unable to instantiate factory class [" + factoryClassName + "]", e); 181 | } 182 | 183 | return clientFactory; 184 | } 185 | 186 | public void stop() { 187 | if (client != null) { 188 | log.debug("Shutting down Memcache client"); 189 | client.shutdown(); 190 | } 191 | client = null; 192 | } 193 | 194 | /** 195 | * According to the hibernate reference docs, MinimalPutsEnabledByDefault should be true for distributed caches. 196 | * 197 | * @return true 198 | */ 199 | public boolean isMinimalPutsEnabledByDefault() { 200 | return true; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/com/googlecode/hibernate/memcached/MemcachedCache.java: -------------------------------------------------------------------------------- 1 | /* Copyright 2008 Ray Krueger 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | package com.googlecode.hibernate.memcached; 16 | 17 | import org.hibernate.cache.Cache; 18 | import org.hibernate.cache.CacheException; 19 | import org.slf4j.Logger; 20 | import org.slf4j.LoggerFactory; 21 | 22 | import java.util.Map; 23 | 24 | /** 25 | * Wrapper around MemcachedClient instance to provide the bridge between Hiberante and Memcached. 26 | * Uses the regionName given by Hibernate via the {@link com.googlecode.hibernate.memcached.MemcachedCacheProvider} 27 | * when generating cache keys. 28 | * All cache operations rely on using a {@link com.googlecode.hibernate.memcached.KeyStrategy} 29 | * to generate cache keys for use in memcached. 30 | *

31 | * Support for the {@link #clear()} operation is disabled by default.
32 | * There is no way for this instance of MemcachedCache to know what cache values to "clear" in a given Memcached instance. 33 | * Clear functionality is implemented by incrementing a "clearIndex" value that is always included in the cache-key generation. 34 | * When clear is called the memcached increment function is used to increment the global clean index. When clear is enabled, 35 | * every cache action taken starts with a call to memcached to 'get' the clearIndex counter. That value is then 36 | * applied to the cache key for the cache operation being taken. When the clearIndex is incremented this causes 37 | * the MemcachedCache to generate different cache-keys than it was before. This results in previously cached data being 38 | * abandoned in the cache, and left for memcached to deal with. 39 | *

40 | * For these reasons it is not recommended to rely on clear() as a regular production functionality, 41 | * it is very expensive and generally not very useful anyway. 42 | *

43 | * The MemcachedCache treats Hibernate cache regions as namespaces in Memcached. For more information see the 44 | * memcached FAQ. 45 | * 46 | * @author Ray Krueger 47 | */ 48 | public class MemcachedCache implements Cache { 49 | 50 | private final Logger log = LoggerFactory.getLogger(MemcachedCache.class); 51 | 52 | private final String regionName; 53 | private final Memcache memcache; 54 | private final String clearIndexKey; 55 | private int cacheTimeSeconds = 300; 56 | private boolean clearSupported = false; 57 | private KeyStrategy keyStrategy = new Sha1KeyStrategy(); 58 | private boolean dogpilePreventionEnabled = false; 59 | private double dogpilePreventionExpirationFactor = 2; 60 | 61 | public static final Integer DOGPILE_TOKEN = 0; 62 | 63 | public MemcachedCache(String regionName, Memcache memcachedClient) { 64 | this.regionName = (regionName != null) ? regionName : "default"; 65 | this.memcache = memcachedClient; 66 | clearIndexKey = this.regionName.replaceAll("\\s", "") + ":index_key"; 67 | } 68 | 69 | public int getCacheTimeSeconds() { 70 | return cacheTimeSeconds; 71 | } 72 | 73 | public void setCacheTimeSeconds(int cacheTimeSeconds) { 74 | this.cacheTimeSeconds = cacheTimeSeconds; 75 | } 76 | 77 | public boolean isClearSupported() { 78 | return clearSupported; 79 | } 80 | 81 | public void setClearSupported(boolean clearSupported) { 82 | this.clearSupported = clearSupported; 83 | } 84 | 85 | public boolean isDogpilePreventionEnabled() { 86 | return dogpilePreventionEnabled; 87 | } 88 | 89 | public void setDogpilePreventionEnabled(boolean dogpilePreventionEnabled) { 90 | this.dogpilePreventionEnabled = dogpilePreventionEnabled; 91 | } 92 | 93 | public double getDogpilePreventionExpirationFactor() { 94 | return dogpilePreventionExpirationFactor; 95 | } 96 | 97 | public void setDogpilePreventionExpirationFactor(double dogpilePreventionExpirationFactor) { 98 | if (dogpilePreventionExpirationFactor < 1.0) { 99 | throw new IllegalArgumentException("dogpilePreventionExpirationFactor must be greater than 1.0"); 100 | } 101 | this.dogpilePreventionExpirationFactor = dogpilePreventionExpirationFactor; 102 | } 103 | 104 | private String dogpileTokenKey(String objectKey) { 105 | return objectKey + ".dogpileTokenKey"; 106 | } 107 | 108 | private Object memcacheGet(Object key) { 109 | String objectKey = toKey(key); 110 | 111 | if (dogpilePreventionEnabled) { 112 | return getUsingDogpilePrevention(objectKey); 113 | 114 | } else { 115 | log.debug("Memcache.get({})", objectKey); 116 | return memcache.get(objectKey); 117 | } 118 | } 119 | 120 | private Object getUsingDogpilePrevention(String objectKey) { 121 | Map multi; 122 | 123 | String dogpileKey = dogpileTokenKey(objectKey); 124 | log.debug("Checking dogpile key: [{}]", dogpileKey); 125 | 126 | log.debug("Memcache.getMulti({}, {})", objectKey, dogpileKey); 127 | multi = memcache.getMulti(dogpileKey, objectKey); 128 | 129 | if ((multi == null) || (multi.get(dogpileKey) == null)) { 130 | log.debug("Dogpile key ({}) not found updating token and returning null", dogpileKey); 131 | memcache.set(dogpileKey, cacheTimeSeconds, DOGPILE_TOKEN); 132 | return null; 133 | } 134 | 135 | return multi.get(objectKey); 136 | } 137 | 138 | private void memcacheSet(Object key, Object o) { 139 | String objectKey = toKey(key); 140 | 141 | int cacheTime = cacheTimeSeconds; 142 | 143 | if (dogpilePreventionEnabled) { 144 | String dogpileKey = dogpileTokenKey(objectKey); 145 | log.debug("Dogpile prevention enabled, setting token and adjusting object cache time. Key: [{}]", dogpileKey); 146 | memcache.set(dogpileKey, cacheTimeSeconds, DOGPILE_TOKEN); 147 | cacheTime = (int) (cacheTimeSeconds * dogpilePreventionExpirationFactor); 148 | } 149 | 150 | log.debug("Memcache.set({})", objectKey); 151 | 152 | memcache.set(objectKey, cacheTime, o); 153 | } 154 | 155 | private String toKey(Object key) { 156 | return keyStrategy.toKey(regionName, getClearIndex(), key); 157 | } 158 | 159 | public Object read(Object key) throws CacheException { 160 | return memcacheGet(key); 161 | } 162 | 163 | public Object get(Object key) throws CacheException { 164 | return memcacheGet(key); 165 | } 166 | 167 | public void put(Object key, Object value) throws CacheException { 168 | memcacheSet(key, value); 169 | } 170 | 171 | public void update(Object key, Object value) throws CacheException { 172 | put(key, value); 173 | } 174 | 175 | public void remove(Object key) throws CacheException { 176 | memcache.delete(toKey(key)); 177 | } 178 | 179 | /** 180 | * Clear functionality is disabled by default. 181 | * Read this class's javadoc for more detail. 182 | * 183 | * @throws CacheException 184 | * @see com.googlecode.hibernate.memcached.MemcachedCache 185 | */ 186 | public void clear() throws CacheException { 187 | if (clearSupported) { 188 | memcache.incr(clearIndexKey, 1, 1); 189 | } 190 | } 191 | 192 | public void destroy() throws CacheException { 193 | //the client is shared by default with all cache instances, so don't shut it down. 194 | } 195 | 196 | public void lock(Object key) throws CacheException { 197 | } 198 | 199 | public void unlock(Object key) throws CacheException { 200 | } 201 | 202 | public long nextTimestamp() { 203 | return System.currentTimeMillis() / 100; 204 | } 205 | 206 | public int getTimeout() { 207 | return cacheTimeSeconds; 208 | } 209 | 210 | public String getRegionName() { 211 | return regionName; 212 | } 213 | 214 | public long getSizeInMemory() { 215 | return -1; 216 | } 217 | 218 | public long getElementCountInMemory() { 219 | return -1; 220 | } 221 | 222 | public long getElementCountOnDisk() { 223 | return -1; 224 | } 225 | 226 | public Map toMap() { 227 | throw new UnsupportedOperationException(); 228 | } 229 | 230 | public String toString() { 231 | return "Memcached (" + regionName + ")"; 232 | } 233 | 234 | private long getClearIndex() { 235 | Long index = null; 236 | 237 | if (clearSupported) { 238 | Object value = memcache.get(clearIndexKey); 239 | if (value != null) { 240 | if (value instanceof String) { 241 | index = Long.valueOf((String) value); 242 | } else if (value instanceof Long) { 243 | index = (Long) value; 244 | } else { 245 | throw new IllegalArgumentException( 246 | "Unsupported type [" + value.getClass() + "] found for clear index at cache key [" + clearIndexKey + "]"); 247 | } 248 | } 249 | 250 | if (index != null) { 251 | return index; 252 | } 253 | } 254 | 255 | return 0L; 256 | } 257 | 258 | public KeyStrategy getKeyStrategy() { 259 | return keyStrategy; 260 | } 261 | 262 | public void setKeyStrategy(KeyStrategy keyStrategy) { 263 | this.keyStrategy = keyStrategy; 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.googlecode 5 | hibernate-memcached 6 | 1.4-SNAPSHOT 7 | hibernate-memcached 8 | A library for using Memcached as a second level distributed cache in Hibernate. 9 | http://code.google.com/p/hibernate-memcached/ 10 | jar 11 | 12 | 13 | 14 | Ray Krueger 15 | http://raykrueger.blogspot.com/ 16 | -6 17 | 18 | 19 | 20 | 21 | 22 | Apache License, Version 2.0 23 | http://www.apache.org/licenses/LICENSE-2.0 24 | 25 | 26 | 27 | 28 | 29 | mine 30 | mine 31 | https://github.com/raykrueger/hibernate-memcached 32 | 33 | 34 | maven2-repository 35 | Java.net Repository for Maven 36 | http://download.java.net/maven/2/ 37 | 38 | 39 | couchbase 40 | Spymemcached repository 41 | http://files.couchbase.com/maven2/ 42 | 43 | 44 | 45 | 46 | scm:git:git@github.com:raykrueger/hibernate-memcached.git 47 | scm:git:git@github.com:raykrueger/hibernate-memcached.git 48 | http://github.com/raykrueger/hibernate-memcached/ 49 | 50 | 51 | 52 | 1.4 53 | 1.8.5 54 | 4.8.2 55 | 56 | 57 | 58 | 59 | ossrh 60 | https://oss.sonatype.org/content/repositories/snapshots 61 | 62 | 63 | ossrh 64 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 65 | 66 | 67 | 68 | 69 | 70 | 71 | org.apache.maven.plugins 72 | maven-source-plugin 73 | 2.2.1 74 | 75 | 76 | attach-sources 77 | 78 | jar-no-fork 79 | 80 | 81 | 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-javadoc-plugin 86 | 2.9.1 87 | 88 | 89 | attach-javadocs 90 | 91 | jar 92 | 93 | 94 | 95 | 96 | 97 | org.apache.maven.plugins 98 | maven-compiler-plugin 99 | 2.3.2 100 | 101 | 1.5 102 | 1.5 103 | UTF-8 104 | 105 | 106 | 107 | maven-assembly-plugin 108 | 109 | 110 | bin 111 | src 112 | 113 | 114 | 115 | 116 | 117 | org.codehaus.gmaven 118 | gmaven-plugin 119 | ${gmaven.version} 120 | 121 | 122 | 123 | generateTestStubs 124 | testCompile 125 | 126 | 127 | 128 | 129 | 1.7 130 | src/main/groovy 131 | 132 | 133 | 134 | org.codehaus.gmaven.runtime 135 | gmaven-runtime-1.7 136 | ${gmaven.version} 137 | 138 | 139 | org.codehaus.groovy 140 | groovy-all 141 | 142 | 143 | 144 | 145 | org.codehaus.groovy 146 | groovy-all 147 | ${groovy.version} 148 | 149 | 150 | 151 | 164 | 165 | org.apache.maven.plugins 166 | maven-scm-plugin 167 | 1.2 168 | 169 | 170 | org.apache.maven.scm 171 | maven-scm-provider-gitexe 172 | 1.2 173 | 174 | 175 | 176 | 177 | org.apache.maven.plugins 178 | maven-release-plugin 179 | 2.0-beta-9 180 | 181 | 182 | org.apache.maven.plugins 183 | maven-scm-plugin 184 | 1.2 185 | 186 | 187 | org.apache.maven.scm 188 | maven-scm-api 189 | 1.2 190 | 191 | 192 | 193 | 194 | org.sonatype.plugins 195 | nexus-staging-maven-plugin 196 | 1.6.3 197 | true 198 | 199 | ossrh 200 | https://oss.sonatype.org/ 201 | true 202 | 203 | 204 | 205 | org.apache.maven.plugins 206 | maven-gpg-plugin 207 | 1.5 208 | 209 | 210 | sign-artifacts 211 | verify 212 | 213 | sign 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | spy 224 | spymemcached 225 | 2.8.1 226 | 227 | 228 | com.danga 229 | java_memcached 230 | 2.0.1 231 | 232 | 233 | org.slf4j 234 | slf4j-api 235 | 1.6.4 236 | 237 | 238 | org.hibernate 239 | hibernate-core 240 | 3.6.7.Final 241 | provided 242 | 243 | 244 | xml-apis 245 | xml-apis 246 | 247 | 248 | org.slf4j 249 | slf4j-api 250 | 251 | 252 | 253 | 254 | javassist 255 | javassist 256 | 3.10.0.GA 257 | 258 | provided 259 | 260 | 261 | org.hibernate 262 | hibernate-annotations 263 | 3.5.6-Final 264 | test 265 | 266 | 267 | org.slf4j 268 | slf4j-log4j12 269 | 1.6.4 270 | test 271 | 272 | 273 | junit 274 | junit 275 | ${junit.version} 276 | test 277 | 278 | 279 | hsqldb 280 | hsqldb 281 | 1.8.0.7 282 | test 283 | 284 | 285 | org.codehaus.groovy 286 | groovy-all 287 | ${groovy.version} 288 | test 289 | 290 | 291 | 292 | --------------------------------------------------------------------------------