001package org.avaje.ebeanorm.hazelcast; 002 003import com.avaje.ebean.BackgroundExecutor; 004import com.avaje.ebean.cache.ServerCache; 005import com.avaje.ebean.cache.ServerCacheFactory; 006import com.avaje.ebean.cache.ServerCacheOptions; 007import com.avaje.ebean.cache.ServerCacheType; 008import com.avaje.ebean.config.ServerConfig; 009import com.avaje.ebeaninternal.server.cache.DefaultServerCache; 010import com.hazelcast.client.HazelcastClient; 011import com.hazelcast.client.config.ClientConfig; 012import com.hazelcast.config.Config; 013import com.hazelcast.core.Hazelcast; 014import com.hazelcast.core.HazelcastInstance; 015import com.hazelcast.core.IMap; 016import com.hazelcast.core.ITopic; 017import com.hazelcast.core.Message; 018import com.hazelcast.core.MessageListener; 019import org.slf4j.Logger; 020import org.slf4j.LoggerFactory; 021 022import java.util.Properties; 023import java.util.concurrent.ConcurrentHashMap; 024 025/** 026 * Factory for creating the various caches. 027 */ 028public class HzCacheFactory implements ServerCacheFactory { 029 030 /** 031 * This explicitly uses the common "org.avaje.ebean.cache" namespace. 032 */ 033 private static final Logger logger = LoggerFactory.getLogger("org.avaje.ebean.cache.HzCacheFactory"); 034 035 private final ConcurrentHashMap<String,HzQueryCache> queryCaches; 036 037 private final HazelcastInstance instance; 038 039 /** 040 * Topic used to broadcast query cache invalidation. 041 */ 042 private final ITopic<String> queryCacheInvalidation; 043 044 private final BackgroundExecutor executor; 045 046 public HzCacheFactory(ServerConfig serverConfig, BackgroundExecutor executor) { 047 048 this.executor = executor; 049 this.queryCaches = new ConcurrentHashMap<String, HzQueryCache>(); 050 051 if (System.getProperty("hazelcast.logging.type") == null) { 052 System.setProperty("hazelcast.logging.type", "slf4j"); 053 } 054 055 Object configuration = serverConfig.getServiceObject("hazelcastConfiguration"); 056 if (configuration != null) { 057 // explicit configuration probably set via DI 058 if (configuration instanceof ClientConfig) { 059 instance = HazelcastClient.newHazelcastClient((ClientConfig)configuration); 060 } else if (configuration instanceof Config) { 061 instance = Hazelcast.newHazelcastInstance((Config)configuration); 062 } else { 063 throw new IllegalArgumentException("Invalid Hazelcast configuration type "+configuration.getClass()); 064 } 065 } else { 066 // implicit configuration via hazelcast-client.xml or hazelcast.xml 067 if (isServerMode(serverConfig.getProperties())) { 068 instance = Hazelcast.newHazelcastInstance(); 069 } else { 070 instance = HazelcastClient.newHazelcastClient(); 071 } 072 } 073 074 queryCacheInvalidation = instance.getReliableTopic("queryCacheInvalidation"); 075 queryCacheInvalidation.addMessageListener(new MessageListener<String>() { 076 @Override 077 public void onMessage(Message<String> message) { 078 processInvalidation(message.getMessageObject()); 079 } 080 }); 081 } 082 083 /** 084 * Return true if hazelcast should be used in server mode. 085 */ 086 private boolean isServerMode(Properties properties) { 087 return properties != null && properties.getProperty("ebean.hazelcast.servermode","").equals("true"); 088 } 089 090 @Override 091 public ServerCache createCache(ServerCacheType type, String key, ServerCacheOptions options) { 092 093 switch (type) { 094 case QUERY: 095 return createQueryCache(key, options); 096 default: 097 return createNormalCache(type, key, options); 098 } 099 } 100 101 private ServerCache createNormalCache(ServerCacheType type, String key, ServerCacheOptions options) { 102 103 String fullName = type.name() + "-" + key; 104 logger.debug("get cache [{}]", fullName); 105 IMap<Object, Object> map = instance.getMap(fullName); 106 return new HzCache(map); 107 } 108 109 private ServerCache createQueryCache(String key, ServerCacheOptions options) { 110 111 synchronized (this) { 112 HzQueryCache cache = queryCaches.get(key); 113 if (cache == null) { 114 logger.debug("create query cache [{}]", key); 115 cache = new HzQueryCache(key, options); 116 cache.periodicTrim(executor); 117 queryCaches.put(key, cache); 118 } 119 return cache; 120 } 121 } 122 123 /** 124 * Extends normal default implementation with notification of clear() to cluster. 125 */ 126 private class HzQueryCache extends DefaultServerCache { 127 128 HzQueryCache(String name, ServerCacheOptions options) { 129 super(name, options); 130 } 131 132 @Override 133 public void clear() { 134 super.clear(); 135 sendInvalidation(name); 136 } 137 138 /** 139 * Process the invalidation message coming from the cluster. 140 */ 141 private void invalidate() { 142 super.clear(); 143 } 144 } 145 146 /** 147 * Send the invalidation message to all members of the cluster. 148 */ 149 private void sendInvalidation(String key) { 150 queryCacheInvalidation.publish(key); 151 } 152 153 /** 154 * Process a remote query cache invalidation. 155 */ 156 private void processInvalidation(String cacheName) { 157 HzQueryCache cache = queryCaches.get(cacheName); 158 if (cache != null) { 159 cache.invalidate(); 160 } 161 } 162 163}