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}