/* * Copyright 2013 cruxframework.org. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.cruxframework.crux.core.server.rest.state; import java.io.Serializable; import java.util.HashSet; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jgroups.blocks.Cache; import org.jgroups.blocks.ReplCache; /** * It is a very basic implementation of ResourceStateHandler interface for clustered environments. * This implementation is based on JGroups ReplCache component and can be used as basis for most * complete implementations on top of more powerful cache systems, like Infinispan, EhCache, OsCache, * JCS or any other Cache system you prefer. If you choose to use this implementation, you must * include jgroups.jar on your classpath. * * To configure the cache, you can create a file named ClusteredCacheConfig.properties and configure * the following properties: * * channelConfigPropertyFile - JGroups channel config file name * rpcTimeout - Timeout for replCache rpc calls * useL1Cache - To enable or disable L1 Cache * l1ReapingInterval - If L1 cache is enabled, the interval to run the Expired Values Cleaner Thread for L1 Cache * l1MaxNumberOfEntries - If L1 cache is enabled, the max number of entries for L1 Cache * l2ReapingInterval - The interval to run the Expired Values Cleaner Thread for L2 Cache * l1MaxNumberOfEntries - The max number of entries for L2 Cache * clusterName - The name of the cluster to be used by this cache * replCount - The number of nodes in cluster where the information will be replicated * * @author Thiago da Rosa de Bustamante */ public class ClusteredResourceStateHandler implements ResourceStateHandler { private static final Log logger = LogFactory.getLog(ClusteredResourceStateHandler.class); private ReplCache<String, CacheEntry> cache; private short replCount; public static class CacheEntry implements ResourceState, Serializable { private static final long serialVersionUID = -7144309067971959838L; private final long dateModifiedMilis; private final long expires; private final String etag; private CacheEntry(long dateModifiedMilis, long expires, String etag) { this.dateModifiedMilis = dateModifiedMilis; this.expires = expires; this.etag = etag; } @Override public long getDateModified() { return dateModifiedMilis; } @Override public String getEtag() { return etag; } @Override public boolean isExpired() { return System.currentTimeMillis() >= expires; } } /** * */ public ClusteredResourceStateHandler() { try { ClusteredCacheConfig config = ClusteredCacheConfigurationFactory.getConfigurations(); replCount = Short.parseShort(config.replCount()); cache = new ReplCache<String, CacheEntry>(config.channelConfigPropertyFile(), config.clusterName()); cache.setMigrateData(true); cache.setCallTimeout(Integer.parseInt(config.rpcTimeout())); cache.setCachingTime(Integer.parseInt(config.cachingTime())); cache.setDefaultReplicationCount(replCount); if (Boolean.parseBoolean(config.useL1Cache())) { Cache<String,CacheEntry> l1Cache=new Cache<String,CacheEntry>(); cache.setL1Cache(l1Cache); int l1ReapingInterval = Integer.parseInt(config.l1ReapingInterval()); if (l1ReapingInterval > 0) { l1Cache.enableReaping(l1ReapingInterval); } int l1MaxNumberOfEntries = Integer.parseInt(config.l1MaxNumberOfEntries()); if (l1MaxNumberOfEntries > 0) { l1Cache.setMaxNumberOfEntries(l1MaxNumberOfEntries); } } Cache<String,ReplCache.Value<CacheEntry>> l2Cache=cache.getL2Cache(); int l2ReapingInterval = Integer.parseInt(config.l2ReapingInterval()); if (l2ReapingInterval > 0) { l2Cache.enableReaping(l2ReapingInterval); } int l2MaxNumberOfEntries = Integer.parseInt(config.l2MaxNumberOfEntries()); if (l2MaxNumberOfEntries > 0) { l2Cache.setMaxNumberOfEntries(l2MaxNumberOfEntries); } cache.start(); } catch (Exception e) { logger.error("Error connecting to resources distributed cache", e); e.printStackTrace(); } } @Override public ResourceState add(String uri, long dateModified, long expires, String etag) { CacheEntry cacheEntry = new CacheEntry(dateModified, expires, etag); cache.put(uri, cacheEntry, replCount, expires);//(key, val, repl_count, timeout, synchronous) return cacheEntry; } @Override public ResourceState get(String uri) { return cache.get(uri); } @Override public void remove(String uri) { cache.remove(uri); } @Override public void removeSegments(String... baseURIs) { Set<String> keys = cache.getL2Cache().getInternalMap().keySet(); Set<String> keysToRemove = new HashSet<String>(); for (String key : keys) { for (String baseURI: baseURIs) { if (key.startsWith(baseURI)) { keysToRemove.add(key); break; } } } for (String key : keysToRemove) { cache.remove(key, true); } } @Override public void clear() { cache.clear(); } }