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}