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 hazelcastInstance = serverConfig.getServiceObject("hazelcast"); 056 if (hazelcastInstance != null) { 057 instance = (HazelcastInstance)hazelcastInstance; 058 } else { 059 instance = createInstance(serverConfig); 060 } 061 062 queryCacheInvalidation = instance.getReliableTopic("queryCacheInvalidation"); 063 queryCacheInvalidation.addMessageListener(new MessageListener<String>() { 064 @Override 065 public void onMessage(Message<String> message) { 066 processInvalidation(message.getMessageObject()); 067 } 068 }); 069 } 070 071 /** 072 * Create a new HazelcastInstance based on configuration from serverConfig. 073 */ 074 private HazelcastInstance createInstance(ServerConfig serverConfig) { 075 Object configuration = serverConfig.getServiceObject("hazelcastConfiguration"); 076 if (configuration != null) { 077 // explicit configuration probably set via DI 078 if (configuration instanceof ClientConfig) { 079 return HazelcastClient.newHazelcastClient((ClientConfig) configuration); 080 } else if (configuration instanceof Config) { 081 return Hazelcast.newHazelcastInstance((Config) configuration); 082 } else { 083 throw new IllegalArgumentException("Invalid Hazelcast configuration type " + configuration.getClass()); 084 } 085 } else { 086 // implicit configuration via hazelcast-client.xml or hazelcast.xml 087 if (isServerMode(serverConfig.getProperties())) { 088 return Hazelcast.newHazelcastInstance(); 089 } else { 090 return HazelcastClient.newHazelcastClient(); 091 } 092 } 093 } 094 095 /** 096 * Return true if hazelcast should be used in server mode. 097 */ 098 private boolean isServerMode(Properties properties) { 099 return properties != null && properties.getProperty("ebean.hazelcast.servermode","").equals("true"); 100 } 101 102 @Override 103 public ServerCache createCache(ServerCacheType type, String key, ServerCacheOptions options) { 104 105 switch (type) { 106 case QUERY: 107 return createQueryCache(key, options); 108 default: 109 return createNormalCache(type, key, options); 110 } 111 } 112 113 private ServerCache createNormalCache(ServerCacheType type, String key, ServerCacheOptions options) { 114 115 String fullName = type.name() + "-" + key; 116 logger.debug("get cache [{}]", fullName); 117 IMap<Object, Object> map = instance.getMap(fullName); 118 return new HzCache(map); 119 } 120 121 private ServerCache createQueryCache(String key, ServerCacheOptions options) { 122 123 synchronized (this) { 124 HzQueryCache cache = queryCaches.get(key); 125 if (cache == null) { 126 logger.debug("create query cache [{}]", key); 127 cache = new HzQueryCache(key, options); 128 cache.periodicTrim(executor); 129 queryCaches.put(key, cache); 130 } 131 return cache; 132 } 133 } 134 135 /** 136 * Extends normal default implementation with notification of clear() to cluster. 137 */ 138 private class HzQueryCache extends DefaultServerCache { 139 140 HzQueryCache(String name, ServerCacheOptions options) { 141 super(name, options); 142 } 143 144 @Override 145 public void clear() { 146 super.clear(); 147 sendInvalidation(name); 148 } 149 150 /** 151 * Process the invalidation message coming from the cluster. 152 */ 153 private void invalidate() { 154 super.clear(); 155 } 156 } 157 158 /** 159 * Send the invalidation message to all members of the cluster. 160 */ 161 private void sendInvalidation(String key) { 162 queryCacheInvalidation.publish(key); 163 } 164 165 /** 166 * Process a remote query cache invalidation. 167 */ 168 private void processInvalidation(String cacheName) { 169 HzQueryCache cache = queryCaches.get(cacheName); 170 if (cache != null) { 171 cache.invalidate(); 172 } 173 } 174 175}