/** * */ package com.sun.faban.driver.transport.asynchronous; import java.io.InputStream; import java.lang.management.ManagementFactory; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import javax.jms.Message; import javax.management.MBeanServer; import net.sf.ehcache.Cache; import net.sf.ehcache.CacheManager; import net.sf.ehcache.Element; import net.sf.ehcache.management.ManagementService; import org.apache.commons.math.stat.Frequency; import org.apache.commons.math.stat.descriptive.DescriptiveStatistics; import com.sun.faban.driver.DriverContext; /** * @author Noah Campbell * @param <T> result * @param <K> message * */ public class MessageCacheTraceRegistry<T, K extends Message> implements TraceRegistry<T, K> { static private AtomicLong traceCounter = new AtomicLong(100000); static private ScheduledExecutorService scheduler; static private DescriptiveStatistics cacheStatistics = DescriptiveStatistics.newInstance(); static private Frequency roundtripFrequence = new Frequency(); static private CacheManager responseCacheManager; private Cache cache; private DriverContext context; /** * @param ctx * @throws RegistryConfigurationException */ public MessageCacheTraceRegistry(DriverContext ctx) throws RegistryConfigurationException { this.context = ctx; scheduler = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors() * 4); synchronized (MessageCacheTraceRegistry.class) { if(responseCacheManager == null) { InputStream ehcacheConfig= getClass().getResourceAsStream("/ehcache.xml"); if(ehcacheConfig == null) { throw new IllegalStateException("Unable to get cache configuration."); } responseCacheManager = CacheManager.create(ehcacheConfig); try { ehcacheConfig.close(); } catch (Exception e) { ctx.getLogger().warning("Unable to close cache configuration. May not affect run"); } StringBuilder builder = new StringBuilder(); builder.append("Configured Caches:").append('\n'); for(String name : responseCacheManager.getCacheNames()) { builder.append('\t').append(name).append('\n'); } ctx.getLogger().info(builder.toString()); ctx.getLogger().info("Registered MBean Server: " + responseCacheManager.getName()); MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer(); ManagementService.registerMBeans(responseCacheManager, mBeanServer, false, false, false, true); } } Cache cache = responseCacheManager.getCache("JMSDriverDistributedCache"); if(cache == null) { throw new RegistryConfigurationException("Unable to get cache"); } this.cache = cache; } /** * @see com.sun.faban.driver.transport.asynchronous.TraceRegistry#compileResults(int, java.util.concurrent.TimeUnit) */ public Object compileResults(int time, TimeUnit unit) { // TODO Auto-generated method stub return null; } /** * @see com.sun.faban.driver.transport.asynchronous.TraceRegistry#isComplete(com.sun.faban.driver.transport.asynchronous.Trace) */ public boolean isComplete(Trace<K> trace) { return cache.isKeyInCache(trace.getIdentifier()); } /** * @param payload The message payload * @return trace A trace object * @throws DyeingException * @see com.sun.faban.driver.transport.asynchronous.TraceRegistry#registerAndDye(java.lang.Object) */ public Trace<K> registerAndDye(K payload) throws DyeingException { try { String correlationId = payload.getJMSCorrelationID(); // set the JMSCorrelationID if it's not set. if(correlationId == null) { correlationId = context.getDriverName() + ":" + context.getAgentId() + ":" + Long.toString(traceCounter.incrementAndGet()); payload.setJMSCorrelationID(correlationId); } return new MessageTrace(correlationId, payload); } catch (Exception e) { throw new DyeingException(e); } } /** * @see com.sun.faban.driver.transport.asynchronous.TraceRegistry#waitForCompletion(com.sun.faban.driver.transport.asynchronous.Trace, int, java.util.concurrent.TimeUnit) */ @SuppressWarnings("unchecked") public T waitForCompletion(Trace<K> trace, int time, TimeUnit unit) throws InterruptedException, ExecutionException { Future<T> future; T result = null; Element element = cache.get(trace); if(element == null) { future = scheduler.submit(new PollingCacheMonitor(trace, 5)); try { result = future.get(time, unit); // wait!!! } catch (TimeoutException e) { context.getLogger().log(Level.FINE, e.getMessage(), e); } } else { result = (T)element.getObjectValue(); // No support for generics } return result; } /** * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("================== Frequency Statistics ==================").append('\n'); builder.append(cacheStatistics.toString()).append('\n'); builder.append("================== Frequency Distribution ================").append('\n'); builder.append(roundtripFrequence.toString()).append('\n'); builder.append("==========================================================").append('\n'); return builder.toString(); } /** * @see com.sun.faban.driver.transport.asynchronous.TraceRegistry#acknowledge(java.lang.String, java.lang.Object) */ public void acknowledge(String traceId, T response) throws MissingDyeException { if(traceId == null || traceId.length() == 0) { throw new MissingDyeException(); } cache.put(new Element(traceId, response)); } /** Message Trace **/ private final class MessageTrace implements Trace<K> { private String id; private K msg; private MessageTrace(String id, K msg) { this.id = id; this.msg = msg; } /** * @see com.sun.faban.driver.transport.asynchronous.Trace#getIdentifier() */ public String getIdentifier() { return id; } /** * @see com.sun.faban.driver.transport.asynchronous.Trace#getPayload() */ public K getPayload() { return msg; } } /** * Pooling response class. * * @author Noah Campbell */ final class PollingCacheMonitor implements Callable<T> { private final String correlationId; private int waitTime; /** * Construct a {@link PollingCacheMonitor} * * @param correlationId * @param time in milliseconds */ private PollingCacheMonitor(Trace<K> trace, int time) { this.correlationId = trace.getIdentifier(); this.waitTime = time; } /** * @see java.util.concurrent.Callable#call() */ @SuppressWarnings("unchecked") public T call() throws Exception { long start = System.currentTimeMillis(); while(!cache.isKeyInCache(correlationId)) { Thread.sleep(waitTime); } Element e = cache.get(correlationId); long duration = System.currentTimeMillis() - start; synchronized(PollingCacheMonitor.class) { cacheStatistics.addValue(duration); roundtripFrequence.addValue(duration); } if(e == null) return null; return (T) e.getObjectValue(); } } private static final Set<Partition> SUPPORTED_PARTITIONS = new HashSet<Partition>(); static { Collections.addAll(SUPPORTED_PARTITIONS, Partition.THREAD, Partition.JVM, Partition.HOST); } /** * @see com.sun.faban.driver.transport.asynchronous.TraceRegistry#getPartitionTolerance() */ public Set<Partition> getPartitionTolerance() { return SUPPORTED_PARTITIONS; } /** * @see com.sun.faban.driver.transport.asynchronous.TraceRegistry#isSafe(com.sun.faban.driver.transport.asynchronous.Partition) */ public boolean isSafe(Partition partition) { return SUPPORTED_PARTITIONS.contains(partition); } }