package com.kendelong.util.concurrency;

import java.util.concurrent.atomic.AtomicInteger;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.lang.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedOperation;
import org.springframework.jmx.export.annotation.ManagedResource;

import com.kendelong.util.monitoring.graphite.GraphiteClient;

/**
 * This is an interceptor that will prevent more than a certain number of threads accessing a given resource
 * at the same time.  Usage could be
 * 
 * <pre>
 * {@code
	<bean class="com.kendelong.util.concurrency.ConcurrencyLimitingAspect" scope="prototype"/>
			
	<bean class="com.kendelong.util.spring.JmxExportingAspectPostProcessor" lazy-init="false">
		<property name="mbeanExporter" ref="mbeanExporter"/>
		<property name="annotationToServiceNames">
			<map>
				<entry key="com.kendelong.util.concurrency.ConcurrencyLimitingAspect" value="concurrencyThrottle" />
			</map>
		</property>
		<property name="jmxDomain" value="app.mystuff"/>
	</bean>
	}
	</pre>
 *
 *
 * @author kdelong
 */
@Aspect
@ManagedResource(description="An interceptor that limits the number of threads that can be in a component at any one time")
public class ConcurrencyLimitingAspect
{
	private final AtomicInteger threadLimit = new AtomicInteger(20);
	private final AtomicInteger threadCount = new AtomicInteger();
	private final AtomicInteger tripCount = new AtomicInteger();
	
	private GraphiteClient graphiteClient;

	@Around("@annotation(com.kendelong.util.concurrency.ConcurrencyThrottle)")
	public Object applyConcurrencyThrottle(ProceedingJoinPoint pjp) throws Throwable
	{
		String key = null;
		if(graphiteClient != null)
		{
			String classKey = StringUtils.substringAfterLast(pjp.getSignature().getDeclaringTypeName(), ".");
			String methodName = pjp.getSignature().getName();
			key = "concurrencyThrottle." + classKey + "." + methodName;
			graphiteClient.increment(key + ".accesses");
		}
		
		Object result;
		try
		{
			int threadNum = threadCount.incrementAndGet();
			if(threadNum > getThreadLimit())
			{
				tripCount.incrementAndGet();
				if(graphiteClient != null) graphiteClient.increment(key + ".trips");
				throw new ConcurrencyLimitExceededException("This thread exceeded the thread limit of " + getThreadLimit());
			}
			result = pjp.proceed();
		}
		finally
		{
			threadCount.decrementAndGet();
		}
		
		return result;
		
	}
	
	@ManagedAttribute(description="The maximum number of threads allowed in the component at one time")
	public int getThreadLimit()
	{
		return threadLimit.get();
	}
	
	@ManagedAttribute
	public void setThreadLimit(int val)
	{
		threadLimit.set(val);
	}
	
	@ManagedAttribute(description="The current number of threads in the component")
	public int getThreadCount()
	{
		return threadCount.get();
	}
	
	@ManagedAttribute(description="The number of accesses that were rejected due to the maximum number of threads being reached")
	public int getTripCount()
	{
		return tripCount.get();
	}
	
	@ManagedOperation(description="Reset the trip count to zero")
	public void resetStatistics()
	{
		tripCount.set(0);
	}

	public GraphiteClient getGraphiteClient()
	{
		return graphiteClient;
	}

	public void setGraphiteClient(GraphiteClient graphiteClient)
	{
		this.graphiteClient = graphiteClient;
	}
	
}

@@ -89,111 +89,8 @@
 r;%0A%0A
-import org.aopalliance.intercept.MethodInterceptor;%0Aimport org.aopalliance.intercept.MethodInvocation;%0A
 impo

fix imports