/**
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.geowebcache.storage.blobstore.memory.distributed;
import java.util.List;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Logger;
import org.geowebcache.storage.TileObject;
import org.geowebcache.storage.blobstore.memory.CacheConfiguration;
import org.geowebcache.storage.blobstore.memory.CacheConfiguration.EvictionPolicy;
import org.geowebcache.storage.blobstore.memory.CacheProvider;
import org.geowebcache.storage.blobstore.memory.CacheStatistics;
import org.geowebcache.storage.blobstore.memory.guava.GuavaCacheProvider;
import org.springframework.beans.factory.DisposableBean;
import com.hazelcast.core.IMap;
import com.hazelcast.map.EntryBackupProcessor;
import com.hazelcast.map.EntryProcessor;
import com.hazelcast.monitor.LocalMapStats;
import com.hazelcast.query.EntryObject;
import com.hazelcast.query.Predicate;
import com.hazelcast.query.PredicateBuilder;
/**
* This class is an implementation of the {@link CacheProvider} interface for a distributed configuration using Hazelcast. This class requires a
* configuration at the GWC startup. The Hazelcast instance used is returned by the {@link HazelcastLoader} object which internally handles the
* configuration for the cache. Note that this cache does not provide access to all the local statistics parameters so some of them will return -1 as
* result. There could happen that the number cache HITS is bigger than the number of total operations. This is caused by the fact that HITS number
* indicates the number of hits on the local entries considering also the requests made by other cluster instances while the total operation count
* indicates only the number of GET operations requested on the local cluster instance.
*
* @author Nicola Lagomarsini Geosolutions
*/
public class HazelcastCacheProvider implements CacheProvider, DisposableBean {
/** {@link Logger} object used for logging operations */
private final static Log LOGGER = LogFactory.getLog(HazelcastCacheProvider.class);
/** Fixed name for the Hazelcast map */
public static final String HAZELCAST_MAP_DEFINITION = "CacheProviderMap";
/** Converter from Mb to Bytes */
public static final long MB_TO_BYTES = 1048576;
/** Name of the {@link CacheProvider} used as Label */
private static final String HAZELCAST_NAME = "Hazelcast Cache";
/** Hazelcast {@link IMap} */
private final IMap<String, TileObject> map;
/** Boolean indicating that the Cache has been configured */
private final boolean configured;
/** Long value indicating the total size in Bytes */
private final long totalSize;
public HazelcastCacheProvider(HazelcastLoader loader) {
configured = loader.isConfigured();
// If the Hazelcast instance is configured, then the other
// cacheProvider parameters are defined
if (configured) {
map = loader.getInstance().getMap(HAZELCAST_MAP_DEFINITION);
totalSize = loader.getInstance().getConfig().getMapConfig(HAZELCAST_MAP_DEFINITION)
.getMaxSizeConfig().getSize()
* MB_TO_BYTES;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Configured Hazelcast Cache");
}
} else {
map = null;
totalSize = 0;
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Hazelcast Cache not configured");
}
}
}
@Override
public TileObject getTileObj(TileObject obj) {
if (configured) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Getting TileObject:" + obj);
}
String key = GuavaCacheProvider.generateTileKey(obj);
return map.get(key);
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Cache not configured");
}
return null;
}
}
@Override
public void putTileObj(TileObject obj) {
if (configured) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Adding TileObject:" + obj);
}
String key = GuavaCacheProvider.generateTileKey(obj);
map.put(key, obj);
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Cache not configured");
}
}
}
@Override
public void removeTileObj(TileObject obj) {
if (configured) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Removing TileObject:" + obj);
}
String key = GuavaCacheProvider.generateTileKey(obj);
map.remove(key);
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Cache not configured");
}
}
}
@Override
public void removeLayer(String layername) {
if (configured) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Removing Layer:" + layername);
}
// Creation of the Predicate
EntryObject e = new PredicateBuilder().getEntryObject();
Predicate predicate = e.get("layer_name").equal(layername);
// Creation of the processor
CacheEntryProcessor entryProcessor = new CacheEntryProcessor();
// Execution of the Processor
map.executeOnEntries(entryProcessor, predicate);
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Cache not configured");
}
}
}
@Override
public void clear() {
if (configured) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Clearing cache");
}
map.clear();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Cache not configured");
}
}
}
@Override
public void reset() {
if (configured) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Resetting cache");
}
map.clear();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Cache not configured");
}
}
}
@Override
public void destroy() throws Exception {
if (configured) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Destroying cache");
}
map.destroy();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Cache not configured");
}
}
}
@Override
public CacheStatistics getStatistics() {
if (configured) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Getting cache statistics");
}
// Getting statistics and then creating a new HazelcastCacheStatistics instance
LocalMapStats localMapStats = map.getLocalMapStats();
CacheStatistics stats = new HazelcastCacheStatistics(localMapStats, totalSize);
return stats;
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Cache not configured");
}
}
return new CacheStatistics();
}
@Override
public void configure(CacheConfiguration configuration) {
}
@Override
public void addUncachedLayer(String layername) {
}
@Override
public void removeUncachedLayer(String layername) {
}
@Override
public boolean containsUncachedLayer(String layername) {
return false;
}
@Override
public List<EvictionPolicy> getSupportedPolicies() {
return null;
}
@Override
public boolean isImmutable() {
return true;
}
@Override
public boolean isAvailable() {
return configured;
}
@Override
public String getName() {
return HAZELCAST_NAME;
}
/**
* {@link CacheStatistics} extensions used for handling local map statistics
*
* @author Nicola Lagomarsini Geosolutions
*/
static class HazelcastCacheStatistics extends CacheStatistics {
public HazelcastCacheStatistics(LocalMapStats localMapStats, long totalSize) {
// Note that HITS indicates all the hits to the local entries, even if the request
// is made from another cluster instance
long hits = localMapStats.getHits();
setHitCount(hits);
// Total indicates the total number of the GET operations made by the local cache
long total = localMapStats.getGetOperationCount();
// Miss count not defined
setMissCount(-1);
setTotalCount(total);
// If miss is not present, rate cannot be calculated
double hitRate = -1;
double missRate = -1;
setHitRate(hitRate);
setMissRate(missRate);
// Setting total cache size
setTotalSize(totalSize);
// Setting actual size
long actualSize = localMapStats.getOwnedEntryMemoryCost();
setActualSize(actualSize);
// Calculation of the memory occupation
int currentMemoryOccupation = (int) (100L - (1L) * (100 * ((1.0d) * (totalSize - actualSize)) / totalSize));
if (currentMemoryOccupation < 0) {
currentMemoryOccupation = 0;
}
setCurrentMemoryOccupation(currentMemoryOccupation);
// Eviction count not defined
setEvictionCount(-1);
}
}
/**
* {@link EntryProcessor} implementation used for removing defined entries
*
* @author Nicola Lagomarsini Geosolutions
*/
static class CacheEntryProcessor implements EntryProcessor<String, TileObject> {
@Override
public Object process(Entry<String, TileObject> entry) {
// By setting the entry value to null the entry is evicted
entry.setValue(null);
return null;
}
@Override
public EntryBackupProcessor<String, TileObject> getBackupProcessor() {
return null;
}
}
}