package org.radargun.service;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.util.HashMap;
import java.util.Map;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import com.arjuna.ats.arjuna.common.arjPropertyManager;
import com.arjuna.ats.arjuna.coordinator.TransactionReaper;
import com.arjuna.ats.arjuna.coordinator.TxControl;
import com.arjuna.ats.internal.arjuna.objectstore.VolatileStore;
import org.infinispan.AdvancedCache;
import org.infinispan.Cache;
import org.infinispan.container.DataContainer;
import org.infinispan.container.entries.InternalCacheEntry;
import org.infinispan.distribution.DataLocality;
import org.infinispan.distribution.DistributionManager;
import org.infinispan.distribution.ch.ConsistentHash;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.remoting.transport.Address;
import org.radargun.Service;
import org.radargun.config.Property;
import org.radargun.logging.Log;
import org.radargun.logging.LogFactory;
import org.radargun.traits.ProvidesTrait;
import org.radargun.utils.TimeService;
import org.radargun.utils.Utils;
import static java.util.concurrent.TimeUnit.MINUTES;
@Service(doc = InfinispanEmbeddedService.SERVICE_DESCRIPTION)
public class InfinispanEmbeddedService {
protected static final String SERVICE_DESCRIPTION = "Service hosting Infinispan in embedded (library) mode.";
static {
// Set up transactional stores for JBoss TS
arjPropertyManager.getCoordinatorEnvironmentBean().setCommunicationStore(VolatileStore.class.getName());
arjPropertyManager.getObjectStoreEnvironmentBean().setObjectStoreType(VolatileStore.class.getName());
}
protected final Log log = LogFactory.getLog(getClass());
protected final boolean trace = log.isTraceEnabled();
protected final InfinispanLifecycle lifecycle;
protected final InfinispanClustered clustered;
protected volatile DefaultCacheManager cacheManager;
protected volatile boolean enlistExtraXAResource;
protected Map<String, Cache> caches = new HashMap<String, Cache>();
@Property(name = Service.FILE, doc = "File used as a configuration for this service.", deprecatedName = "config")
protected String configFile;
@Property(name = "cache", doc = "Name of the main cache. Default is 'testCache'")
protected String cacheName = "testCache";
@Property(doc = "Threads per node - for EvenConsistentHash.")
private int threadsPerNode = -1;
@Property(doc = "Keys per thread - for EvenConsistentHash.")
private int keysPerThread = -1;
public InfinispanEmbeddedService() {
lifecycle = createLifecycle();
clustered = createClustered();
}
protected InfinispanLifecycle createLifecycle() {
return new InfinispanLifecycle(this);
}
protected InfinispanClustered createClustered() {
return new InfinispanClustered(this);
}
@ProvidesTrait
public InfinispanLifecycle getLifecycle() {
return lifecycle;
}
@ProvidesTrait
public InfinispanOperations createBasicOperations() {
return new InfinispanOperations(this);
}
@ProvidesTrait
public InfinispanDebugable createDebugable() {
return new InfinispanDebugable(this);
}
@ProvidesTrait
public InfinispanTransactional createTransactional() {
return new InfinispanTransactional(this);
}
@ProvidesTrait
public InfinispanCacheInfo createCacheInformation() {
return new InfinispanCacheInfo(this);
}
@ProvidesTrait
public InfinispanClustered getClustered() {
return clustered;
}
protected void startCaches() throws Exception {
log.trace("Using config file: " + configFile + " and cache name: " + cacheName);
TxControl.enable();
TransactionReaper.instantiate();
log.tracef("TxControl: %s", TxControl.isEnabled() ? "enabled" : "disabled");
cacheManager = createCacheManager(configFile);
cacheManager.start();
try {
String cacheNames = cacheManager.getDefinedCacheNames();
if (!cacheNames.contains(cacheName))
throw new IllegalStateException("The requested cache(" + cacheName + ") is not defined. Defined cache " +
"names are " + cacheNames);
caches.put(null, cacheManager.getCache(cacheName));
int i = 0;
for (String name : cacheManager.getCacheNames()) {
log.trace(i + " adding cache: " + name);
Cache cache = cacheManager.getCache(name);
// TODO: remove or rename "buckets", and externalize that
caches.put("bucket_" + i++, cache);
caches.put(name, cache);
}
} catch (Exception e) {
log.trace("Failed to start caches", e);
try {
cacheManager.stop();
} catch (Exception se) {
log.error("Failed to stop after start failed", se);
}
throw e;
}
}
protected void stopCaches() {
try {
cacheManager.stop();
} finally {
caches.clear();
forcedCleanup();
clustered.stopped();
}
}
protected void forcedCleanup() {
try {
TxControl.disable(true);
} catch (Exception e) {
log.error("Failed to stop transaction manager", e);
}
try {
TransactionReaper.terminate(true);
} catch (Exception e) {
log.error("Failed to stop transaction reaper", e);
}
String jmxDomain = getJmxDomain();
MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer();
try {
for (ObjectName objectName : mbeanServer.queryNames(new ObjectName(jmxDomain + ":*"), null)) {
try {
mbeanServer.unregisterMBean(objectName);
} catch (Exception e) {
log.warn("Cannot unregister MBean " + objectName, e);
}
}
} catch (MalformedObjectNameException e) {
log.error("Failed to unregister MBeans", e);
}
}
protected String getJmxDomain() {
return cacheManager.getGlobalConfiguration().getJmxDomain();
}
protected DefaultCacheManager createCacheManager(String configFile) throws IOException {
DefaultCacheManager cm = new DefaultCacheManager(configFile, false);
beforeCacheManagerStart(cm);
return cm;
}
protected void beforeCacheManagerStart(DefaultCacheManager cacheManager) {
cacheManager.addListener(getClustered());
}
protected void waitForRehash() throws InterruptedException {
for (String cacheName : cacheManager.getCacheNames()) {
Cache cache = cacheManager.getCache(cacheName);
blockForRehashing(cache);
injectEvenConsistentHash(cache);
}
}
protected void blockForRehashing(Cache<Object, Object> cache) throws InterruptedException {
if (isCacheDistributed(cache)) {
// should we be blocking until all rehashing, etc. has finished?
long gracePeriod = MINUTES.toMillis(15);
long giveup = TimeService.currentTimeMillis() + gracePeriod;
while (!isJoinComplete(cache) && TimeService.currentTimeMillis() < giveup)
Thread.sleep(200);
if (!isJoinComplete(cache)) {
throw new RuntimeException("Caches haven't discovered and joined the cluster even after " + Utils.prettyPrintMillis(gracePeriod));
}
}
}
protected void injectEvenConsistentHash(Cache<Object, Object> cache) {
if (isCacheDistributed(cache)) {
ConsistentHash ch = cache.getAdvancedCache().getDistributionManager().getConsistentHash();
if (ch instanceof EvenSpreadingConsistentHash) {
if (threadsPerNode < 0)
throw new IllegalStateException("When EvenSpreadingConsistentHash is used threadsPerNode must also be set.");
if (keysPerThread < 0)
throw new IllegalStateException("When EvenSpreadingConsistentHash is used must also be set.");
((EvenSpreadingConsistentHash) ch).init(threadsPerNode, keysPerThread);
log.info("Using an even consistent hash!");
}
}
}
public Cache<Object, Object> getCache(String cacheName) {
return caches.get(cacheName);
}
/* API that adapts to Infinispan version */
protected boolean isCacheDistributed(Cache<?, ?> cache) {
return cache.getConfiguration().getCacheMode().isDistributed();
}
protected boolean isCacheClustered(Cache<?, ?> cache) {
return cache.getConfiguration().getCacheMode().isClustered();
}
protected boolean isCacheTransactional(Cache<?, ?> cache) {
return cache.getAdvancedCache().getTransactionManager() != null;
}
protected boolean isCacheAutoCommit(Cache<?, ?> cache) {
return false;
}
protected boolean isJoinComplete(Cache<?, ?> cache) {
return cache.getAdvancedCache().getDistributionManager().isJoinComplete();
}
public int getNumOwners(Cache<?, ?> cache) {
switch (cache.getConfiguration().getCacheMode()) {
case LOCAL:
return 1;
case REPL_SYNC:
case REPL_ASYNC:
return cacheManager.getMembers().size();
case INVALIDATION_SYNC:
case INVALIDATION_ASYNC:
case DIST_SYNC:
case DIST_ASYNC:
return cache.getConfiguration().getNumOwners();
}
throw new IllegalStateException();
}
protected String getKeyInfo(AdvancedCache cache, Object key) {
DistributionManager dm = cache.getDistributionManager();
DataContainer container = cache.getDataContainer();
StringBuilder sb = new StringBuilder(256);
sb.append("Debug info for key ").append(cache.getName()).append(' ').append(key).append(": owners=");
for (Address owner : dm.locate(key)) {
sb.append(owner).append(", ");
}
DataLocality locality = dm.getLocality(key);
sb.append("local=").append(locality.isLocal()).append(", uncertain=").append(locality.isUncertain());
sb.append(", container.").append(key).append('=').append(toString(container.get(key)));
return sb.toString();
}
protected String toString(InternalCacheEntry ice) {
if (ice == null) return null;
StringBuilder sb = new StringBuilder(256);
sb.append(ice.getClass().getSimpleName());
sb.append("[key=").append(ice.getKey()).append(", value=").append(ice.getValue());
return sb.append(']').toString();
}
protected String getCHInfo(DistributionManager dm) {
return "\nCH: " + dm.getConsistentHash();
}
}