/*
 * Copyright: (c) 2004-2011 Mayo Foundation for Medical Education and 
 * Research (MFMER). All rights reserved. MAYO, MAYO CLINIC, and the
 * triple-shield Mayo logo are trademarks and service marks of MFMER.
 *
 * Except as contained in the copyright notice above, or as used to identify 
 * MFMER as the author of this software, the trade names, trademarks, service
 * marks, or product names of the copyright holder shall not be used in
 * advertising, promotion or otherwise in connection with this software without
 * prior written authorization of the copyright holder.
 *
 * 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 edu.mayo.cts2.framework.webapp.rest.controller;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
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 org.apache.commons.lang.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import edu.mayo.cts2.framework.core.timeout.Timeout;
import edu.mayo.cts2.framework.model.exception.ExceptionFactory;
import edu.mayo.cts2.framework.webapp.rest.command.QueryControl;

/**
 * The Class MethodTimingAspect.
 *
 * @author <a href="mailto:kevin.peterson@mayo.edu">Kevin Peterson</a>
 */
@Aspect
@Order(Ordered.LOWEST_PRECEDENCE)
public class MethodTimingAspect {
	
	private ExecutorService executorService = Executors.newCachedThreadPool();
	private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
	
	/**
	 * Execute.
	 *
	 * @param pjp the pjp
	 * @return the object
	 * @throws Throwable the throwable
	 */
	@Around("execution(public *"
	        + " edu.mayo.cts2.framework.webapp.rest.controller.*.*(..,edu.mayo.cts2.framework.webapp.rest.command.QueryControl,..))")
	    public Object execute(final ProceedingJoinPoint pjp) throws Throwable {

		QueryControl queryControl = null;
		
		//this should never happen
		if(ArrayUtils.isEmpty(pjp.getArgs())){
			throw new IllegalStateException("Pointcut failure!");
		}
		
		for(Object arg : pjp.getArgs()){
			if(arg.getClass() == QueryControl.class){
				queryControl = (QueryControl) arg;
				break;
			}
		}
		
		//this also should never happen
		if(queryControl == null){
			throw new IllegalStateException("Pointcut failure!");
		}
		
		final AtomicLong threadId = new AtomicLong(-1); 
		
		Future<Object> future = this.executorService.submit(new Callable<Object>(){

			@Override
			public Object call() {
				try {
					threadId.set(Thread.currentThread().getId());
					
					/*
					 * The model here is that we clear any previous timeout before we launch the job. A design flaw is that we
					 * can't tell if we are clearing a previous timeout that simply hadn't been cleaned up yet, or if we are
					 * clearing a timeout meant for this thread that happened before this thread even launched. The second scenario 
					 * seems unlikely as the minimum timeout is 1 second - hard to believe it would take more than 1 second to 
					 * launch this thread. Plus, this thread would have to launch in the exact window in between the timeout and 
					 * the future.cancel()
					 * 
					 * If the above scenario did defy all odds and happen , it shouldn't cause much harm, as the end result would
					 * be that this thread wouldn't see the cancelled flag - and would churn away for no reason, wasting some cpu
					 * cycles, but doing no other harm.
					 */
					
					Timeout.clearThreadFlag(threadId.get());
					return pjp.proceed();
				} catch (Throwable e) {
					
					if(e instanceof Error){
						throw (Error)e;
					}
					
					if(e instanceof RuntimeException){
						throw (RuntimeException)e;
					}
					
					throw new RuntimeException(e);
				}
			}
		});
		
		long time = queryControl.getTimelimit();

		try {
			if(time < 0){
				return future.get();
			} else {
				return future.get(time, TimeUnit.SECONDS);
			}
		} catch (ExecutionException e) {
			throw e.getCause();
		} catch (TimeoutException e) {
			try
			{
				//Set the flag for the processing thread to read
				Timeout.setTimeLimitExceeded(threadId.get());
				
				//Schedule another future to make sure we don't cause a memory leak if the thread IDs aren't being reused (though, they should be)
				//and therefore don't get cleared up by the next run.  Give the running thread 30 seconds to see the cancelled flag before this 
				//cleanup takes place.
				this.scheduledExecutorService.schedule(new Runnable()
				{
					@Override
					public void run()
					{
						Timeout.clearThreadFlag(threadId.get());
					}
				}, 30, TimeUnit.SECONDS);
				
				//Interrupt the processing thread so it has an opportunity to check the flag and stop.
				future.cancel(true);
			}
			catch (Exception e1)
			{
				// don't think this is possible, but just in case...
			}
			throw ExceptionFactory.createTimeoutException(e.getMessage());
		}
	}
}

@@ -1445,32 +1445,75 @@
 xecutorService;%0A
+import java.util.concurrent.ThreadFactory;%0A
 import java.util
@@ -2346,106 +2346,450 @@
 ool(
-);%0A%09private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1
+new ThreadFactory()%0A%09%7B%0A%09%09@Override%0A%09%09public Thread newThread(Runnable r)%0A%09%09%7B%0A%09%09%09Thread t = new Thread(r);%0A%09%09%09t.setName(%22ProcessingThread-%22 + t.getId());%0A%09%09%09return t;%0A%09%09%7D%0A%09%7D);%0A%09private ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1, new ThreadFactory()%0A%09%7B%0A%09%09@Override%0A%09%09public Thread newThread(Runnable r)%0A%09%09%7B%0A%09%09%09Thread t = new Thread(r);%0A%09%09%09t.setName(%22ExecutionTimer-%22 + t.getId());%0A%09%09%09return t;%0A%09%09%7D%0A%09%7D
 );%0A%09

put in thread names on pooled threads