/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.marmotta.kiwi.infinispan.embedded;
import org.apache.commons.io.IOUtils;
import org.apache.marmotta.kiwi.caching.CacheManager;
import org.apache.marmotta.kiwi.config.KiWiConfiguration;
import org.apache.marmotta.kiwi.infinispan.externalizer.*;
import org.apache.marmotta.kiwi.infinispan.util.AsyncMap;
import org.infinispan.Cache;
import org.infinispan.commons.CacheException;
import org.infinispan.commons.marshall.AdvancedExternalizer;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfiguration;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.context.Flag;
import org.infinispan.distribution.ch.SyncConsistentHashFactory;
import org.infinispan.eviction.EvictionStrategy;
import org.infinispan.lifecycle.ComponentStatus;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* A class for managing the different caches that are used by the triple store.
* <p/>
* Author: Sebastian Schaffert
*/
public class InfinispanEmbeddedCacheManager implements CacheManager {
private static Logger log = LoggerFactory.getLogger(InfinispanEmbeddedCacheManager.class);
private EmbeddedCacheManager cacheManager;
// default configuration: distributed cache, 100000 entries, 300 seconds expiration, 60 seconds idle
private Configuration defaultConfiguration;
private KiWiConfiguration config;
private Map nodeCache, tripleCache, uriCache, literalCache, bnodeCache, nsPrefixCache, nsUriCache, registryCache;
/**
* Create a new cache manager with its own automatically created Infinispan instance.
*
* @param config
*/
public InfinispanEmbeddedCacheManager(KiWiConfiguration config) {
this.config = config;
try {
switch (config.getCacheMode()) {
case DISTRIBUTED:
buildDistributedConfiguration(getExternalizers());
break;
case REPLICATED:
buildReplicatedConfiguration(getExternalizers());
break;
case LOCAL:
buildLocalConfiguration();
break;
}
} catch (IOException ex) {
log.warn("error while building cache cluster configuration, reverting to local cache");
buildLocalConfiguration();
}
}
/**
* Build a local cache configuration.
* <p/>
* In local cache mode, the cache is not shared among the servers in a cluster. Each machine keeps a local cache.
* This allows quick startups and eliminates network traffic in the cluster, but subsequent requests to different
* cluster members cannot benefit from the cached data.
*/
protected void buildLocalConfiguration() {
GlobalConfiguration globalConfiguration = new GlobalConfigurationBuilder()
.classLoader(InfinispanEmbeddedCacheManager.class.getClassLoader())
.globalJmxStatistics()
.jmxDomain("org.apache.marmotta.kiwi")
.allowDuplicateDomains(true)
.build();
defaultConfiguration = new ConfigurationBuilder()
.clustering()
.cacheMode(CacheMode.LOCAL)
.eviction()
.strategy(EvictionStrategy.LIRS)
.maxEntries(100000)
.expiration()
.lifespan(5, TimeUnit.MINUTES)
.maxIdle(1, TimeUnit.MINUTES)
.build();
cacheManager = new DefaultCacheManager(globalConfiguration, defaultConfiguration, true);
log.info("initialised Infinispan local cache manager");
}
/**
* Build a distributed cache configuration.
* <p/>
* In distributed cache mode, the cluster forms a big hash table used as a cache. This allows to make efficient
* use of the large amount of memory available, but requires cache rebalancing and a lot of network transfers,
* especially in case cluster members are restarted often.
*/
protected void buildDistributedConfiguration(AdvancedExternalizer...externalizers) throws IOException {
String jgroupsXml = IOUtils.toString(InfinispanEmbeddedCacheManager.class.getResourceAsStream("/jgroups-kiwi.xml"));
jgroupsXml = jgroupsXml.replaceAll("mcast_addr=\"[0-9.]+\"", String.format("mcast_addr=\"%s\"", config.getClusterAddress()));
jgroupsXml = jgroupsXml.replaceAll("mcast_port=\"[0-9]+\"", String.format("mcast_port=\"%d\"", config.getClusterPort()));
GlobalConfiguration globalConfiguration = new GlobalConfigurationBuilder()
.classLoader(InfinispanEmbeddedCacheManager.class.getClassLoader())
.transport()
.defaultTransport()
.clusterName(config.getClusterName())
.machineId("instance-" + config.getDatacenterId())
.addProperty("configurationXml", jgroupsXml)
.distributedSyncTimeout(config.getClusterTimeout())
.globalJmxStatistics()
.jmxDomain("org.apache.marmotta.kiwi")
.allowDuplicateDomains(true)
.serialization()
.addAdvancedExternalizer(externalizers)
.build();
defaultConfiguration = new ConfigurationBuilder()
.clustering()
.cacheMode(CacheMode.DIST_ASYNC)
.async()
.asyncMarshalling()
.l1()
.lifespan(5, TimeUnit.MINUTES)
.hash()
.numOwners(2)
.numSegments(40)
.consistentHashFactory(new SyncConsistentHashFactory())
.stateTransfer()
.fetchInMemoryState(false)
.timeout(config.getClusterTimeout())
.eviction()
.strategy(EvictionStrategy.LIRS)
.maxEntries(100000)
.expiration()
.lifespan(30, TimeUnit.MINUTES)
.maxIdle(10, TimeUnit.MINUTES)
.build();
cacheManager = new DefaultCacheManager(globalConfiguration, defaultConfiguration, true);
log.info("initialised Infinispan distributed cache manager (cluster name: {})", globalConfiguration.transport().clusterName());
}
/**
* Build a replicated cache configuration.
* <p/>
* In replicated cache mode, each node in the cluster has an identical copy of all cache data. This allows
* very efficient cache lookups and reduces the rebalancing effort, but requires more memory.
*/
protected void buildReplicatedConfiguration(AdvancedExternalizer...externalizers) throws IOException {
String jgroupsXml = IOUtils.toString(InfinispanEmbeddedCacheManager.class.getResourceAsStream("/jgroups-kiwi.xml"));
jgroupsXml = jgroupsXml.replaceAll("mcast_addr=\"[0-9.]+\"", String.format("mcast_addr=\"%s\"", config.getClusterAddress()));
jgroupsXml = jgroupsXml.replaceAll("mcast_port=\"[0-9]+\"", String.format("mcast_port=\"%d\"", config.getClusterPort()));
GlobalConfiguration globalConfiguration = new GlobalConfigurationBuilder()
.classLoader(InfinispanEmbeddedCacheManager.class.getClassLoader())
.transport()
.defaultTransport()
.clusterName(config.getClusterName())
.machineId("instance-" + config.getDatacenterId())
.addProperty("configurationXml", jgroupsXml)
.distributedSyncTimeout(config.getClusterTimeout())
.globalJmxStatistics()
.jmxDomain("org.apache.marmotta.kiwi")
.allowDuplicateDomains(true)
.serialization()
.addAdvancedExternalizer(externalizers)
.build();
defaultConfiguration = new ConfigurationBuilder()
.clustering()
.cacheMode(CacheMode.REPL_ASYNC)
.async()
.asyncMarshalling()
.stateTransfer()
.fetchInMemoryState(false)
.timeout(config.getClusterTimeout())
.eviction()
.strategy(EvictionStrategy.LIRS)
.maxEntries(100000)
.expiration()
.lifespan(30, TimeUnit.MINUTES)
.maxIdle(10, TimeUnit.MINUTES)
.build();
cacheManager = new DefaultCacheManager(globalConfiguration, defaultConfiguration, true);
log.info("initialised Infinispan replicated cache manager (cluster name: {})", globalConfiguration.transport().clusterName());
}
protected boolean isClustered() {
return config.getCacheMode() == org.apache.marmotta.kiwi.config.CacheMode.DISTRIBUTED ||
config.getCacheMode() == org.apache.marmotta.kiwi.config.CacheMode.REPLICATED;
}
private AdvancedExternalizer[] getExternalizers() {
return new AdvancedExternalizer[] {
new TripleExternalizer(),
new UriExternalizer(),
new BNodeExternalizer(),
new StringLiteralExternalizer(),
new DateLiteralExternalizer(),
new BooleanLiteralExternalizer(),
new IntLiteralExternalizer(),
new DoubleLiteralExternalizer()
};
}
/**
* Return the node id -> node cache from the cache manager. This cache is heavily used to lookup
* nodes when querying or loading triples and should therefore have a decent size (default 500.000 elements).
*
* @return an EHCache Cache instance containing the node id -> node mappings
*/
public Map getNodeCache() {
if(nodeCache == null) {
Configuration nodeConfiguration = new ConfigurationBuilder().read(defaultConfiguration)
.eviction()
.maxEntries(1000000)
.expiration()
.lifespan(60, TimeUnit.MINUTES)
.maxIdle(30, TimeUnit.MINUTES)
.build();
cacheManager.defineConfiguration(NODE_CACHE, nodeConfiguration);
nodeCache = new AsyncMap(cacheManager.getCache(NODE_CACHE).getAdvancedCache().withFlags(Flag.SKIP_LOCKING, Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP));
}
return nodeCache;
}
/**
* Return the triple id -> triple cache from the cache manager. This cache is used for speeding up the
* construction of query results.
*
* @return
*/
public Map getTripleCache() {
if(tripleCache == null) {
Configuration tripleConfiguration = new ConfigurationBuilder().read(defaultConfiguration)
.clustering()
.cacheMode(CacheMode.LOCAL)
.eviction()
.maxEntries(config.getTripleCacheSize())
.expiration()
.lifespan(60, TimeUnit.MINUTES)
.maxIdle(30, TimeUnit.MINUTES)
.build();
cacheManager.defineConfiguration(TRIPLE_CACHE, tripleConfiguration);
tripleCache = new AsyncMap(cacheManager.getCache(TRIPLE_CACHE).getAdvancedCache().withFlags(Flag.SKIP_LOCKING, Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP));
}
return tripleCache;
}
/**
* Return the uri -> KiWiUriResource cache from the cache manager. This cache is used when constructing new
* KiWiUriResources to avoid a database lookup.
*
* @return
*/
public Map getUriCache() {
if(uriCache == null) {
Configuration uriConfiguration = new ConfigurationBuilder().read(defaultConfiguration)
.eviction()
.maxEntries(config.getUriCacheSize())
.build();
cacheManager.defineConfiguration(URI_CACHE, uriConfiguration);
uriCache = new AsyncMap(cacheManager.getCache(URI_CACHE).getAdvancedCache().withFlags(Flag.SKIP_LOCKING, Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP));
}
return uriCache;
}
/**
* Return the anonId -> KiWiAnonResource cache from the cache manager. This cache is used when constructing new
* KiWiAnonResources to avoid a database lookup.
*
* @return
*/
public Map getBNodeCache() {
if(bnodeCache == null) {
Configuration bnodeConfiguration = new ConfigurationBuilder().read(defaultConfiguration)
.eviction()
.maxEntries(config.getBNodeCacheSize())
.build();
cacheManager.defineConfiguration(BNODE_CACHE, bnodeConfiguration);
bnodeCache = new AsyncMap(cacheManager.getCache(BNODE_CACHE).getAdvancedCache().withFlags(Flag.SKIP_LOCKING, Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP));
}
return bnodeCache;
}
/**
* Return the literal cache key -> KiWiLiteral cache from the cache manager. This cache is used when constructing new
* KiWiLiterals to avoid a database lookup.
*
* @see org.apache.marmotta.commons.sesame.model.LiteralCommons#createCacheKey(String, java.util.Locale, String)
* @return
*/
public Map getLiteralCache() {
if(literalCache == null) {
Configuration literalConfiguration = new ConfigurationBuilder().read(defaultConfiguration)
.eviction()
.maxEntries(config.getLiteralCacheSize())
.build();
cacheManager.defineConfiguration(LITERAL_CACHE, literalConfiguration);
literalCache = new AsyncMap(cacheManager.getCache(LITERAL_CACHE).getAdvancedCache().withFlags(Flag.SKIP_LOCKING, Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP));
}
return literalCache;
}
/**
* Return the URI -> namespace cache from the cache manager. Used for looking up namespaces
* @return
*/
public Map getNamespaceUriCache() {
if(nsUriCache == null) {
if(isClustered()) {
Configuration nsuriConfiguration = new ConfigurationBuilder().read(defaultConfiguration)
.clustering()
.cacheMode(CacheMode.REPL_ASYNC)
.eviction()
.maxEntries(config.getNamespaceCacheSize())
.expiration()
.lifespan(1, TimeUnit.DAYS)
.build();
cacheManager.defineConfiguration(NS_URI_CACHE, nsuriConfiguration);
} else {
Configuration nsuriConfiguration = new ConfigurationBuilder().read(defaultConfiguration)
.eviction()
.maxEntries(config.getNamespaceCacheSize())
.expiration()
.lifespan(1, TimeUnit.HOURS)
.build();
cacheManager.defineConfiguration(NS_URI_CACHE, nsuriConfiguration);
}
nsUriCache = new AsyncMap(cacheManager.getCache(NS_URI_CACHE).getAdvancedCache().withFlags(Flag.SKIP_LOCKING, Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP));
}
return nsUriCache;
}
/**
* Return the prefix -> namespace cache from the cache manager. Used for looking up namespaces
* @return
*/
public Map getNamespacePrefixCache() {
if(nsPrefixCache == null) {
if(isClustered()) {
Configuration nsprefixConfiguration = new ConfigurationBuilder().read(defaultConfiguration)
.clustering()
.cacheMode(CacheMode.REPL_ASYNC)
.eviction()
.maxEntries(config.getNamespaceCacheSize())
.expiration()
.lifespan(1, TimeUnit.DAYS)
.build();
cacheManager.defineConfiguration(NS_PREFIX_CACHE, nsprefixConfiguration);
} else {
Configuration nsprefixConfiguration = new ConfigurationBuilder().read(defaultConfiguration)
.eviction()
.maxEntries(config.getNamespaceCacheSize())
.expiration()
.lifespan(1, TimeUnit.HOURS)
.build();
cacheManager.defineConfiguration(NS_PREFIX_CACHE, nsprefixConfiguration);
}
nsPrefixCache = cacheManager.getCache(NS_PREFIX_CACHE).getAdvancedCache().withFlags(Flag.SKIP_LOCKING, Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP);
}
return nsPrefixCache;
}
/**
* Create and return the cache used by the CacheTripleRegistry. This is an unlimited synchronous replicated
* cache and should be used with care.
* @return
*/
public Map getRegistryCache() {
if(registryCache == null) {
if(isClustered()) {
Configuration registryConfiguration = new ConfigurationBuilder()
.clustering()
.cacheMode(CacheMode.REPL_SYNC)
.sync()
.replTimeout(15, TimeUnit.SECONDS)
.eviction()
.strategy(EvictionStrategy.NONE)
.build();
cacheManager.defineConfiguration(REGISTRY_CACHE, registryConfiguration);
} else {
Configuration registryConfiguration = new ConfigurationBuilder()
.clustering()
.cacheMode(CacheMode.LOCAL)
.eviction()
.strategy(EvictionStrategy.NONE)
.build();
cacheManager.defineConfiguration(REGISTRY_CACHE, registryConfiguration);
}
registryCache = cacheManager.getCache(REGISTRY_CACHE).getAdvancedCache().withFlags(Flag.SKIP_LOCKING, Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP);
}
return registryCache;
}
/**
* Get the cache with the given name from the cache manager. Can be used to request additional
* caches from the cache manager that are not covered by explicit methods.
*
* @param name
* @return
*/
public synchronized Cache getCacheByName(String name) {
if(!cacheManager.cacheExists(name)) {
cacheManager.defineConfiguration(name, new ConfigurationBuilder().read(defaultConfiguration).build());
}
return cacheManager.getCache(name).getAdvancedCache().withFlags(Flag.SKIP_LOCKING, Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP).getAdvancedCache().withFlags(Flag.SKIP_LOCKING, Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP).getAdvancedCache().withFlags(Flag.SKIP_LOCKING, Flag.SKIP_CACHE_LOAD, Flag.SKIP_REMOTE_LOOKUP);
}
/**
* Clear all caches managed by this cache manager.
*/
public void clear() {
Set<String> set = cacheManager.getCacheNames();
Iterator<String> iterator = set.iterator();
while(iterator.hasNext()){
String cacheName = iterator.next();
Cache<String,Object> cache = cacheManager.getCache(cacheName);
cache.clear();
}
nodeCache = null;
tripleCache = null;
uriCache = null;
literalCache = null;
bnodeCache = null;
nsPrefixCache = null;
nsUriCache = null;
registryCache = null;
}
/**
* Shutdown this cache manager instance. Will shutdown the underlying EHCache cache manager.
*/
public void shutdown() {
try {
if(cacheManager.getStatus() == ComponentStatus.RUNNING) {
log.warn("shutting down cache manager ...");
// if(cacheManager.getTransport() != null) {
// log.info("... shutting down transport ...");
// cacheManager.getTransport().stop();
// }
log.info("... shutting down main component ...");
cacheManager.stop();
log.info("... done!");
}
} catch (CacheException ex) {
log.warn("error shutting down cache: {}", ex.getMessage());
}
}
}