Cleaning of TnR QuartzContextAdapter aspect

package org.parallelj.tracknrestart.aspects;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;

import org.apache.log4j.Logger;
import org.parallelj.Programs;
import org.parallelj.Programs.ProcessHelper;
import org.parallelj.internal.kernel.KCall;
import org.parallelj.internal.kernel.KProcess;
import org.parallelj.internal.reflect.ProgramAdapter.Adapter;
import org.parallelj.tracknrestart.ReturnCodes;
import org.parallelj.tracknrestart.annotations.TrackNRestart;
import org.parallelj.tracknrestart.databinding.ProgramFieldsBinder;
import org.parallelj.tracknrestart.listeners.ForEachListener;
import org.parallelj.tracknrestart.plugins.TrackNRestartPluginAll;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobPersistenceException;

privileged public aspect QuartzContextAdapter percflow (execution(public void Job+.execute(..) throws JobExecutionException)) {

	public static final String RETURN_CODE = "RETURN_CODE";

	declare precedence :
		org.parallelj.tracknrestart.aspects.QuartzContextAdapter,
		org.parallelj.launching.quartz.JobsAdapter;

	static Logger logger = Logger.getLogger("org.parallelj.tracknrestart");

	private X root;

	String result = ReturnCodes.SUCCESS.name();

	//---------------------------------------------------------------------------------------------------

	declare parents:
		(@org.parallelj.launching.QuartzExecution *) implements Job;

	public void Job.execute(JobExecutionContext context) throws JobExecutionException {

		try {
			logger.debug("-----------------------------------------------------------------------------------------------------");
			logger.debug("STARTING //J Root Program "+this.getClass().getName());

			ProcessHelper<?> p = null;
			context.setResult(new JobDataMap());

			p = Programs.as((Adapter) this).execute().join();

			ProgramFieldsBinder.getProgramOutputFields(this, context);

			X xx = (X)this;
			if (xx.exceptionThrown != null){
				throw new JobExecutionException(xx.exceptionThrown);
			}
		} catch (IllegalAccessException e) {
			throw new JobExecutionException(e);
		} catch (NoSuchFieldException e) {
			throw new JobExecutionException(e);
		} finally {
			logger.debug("ENDING   //J Root Program "+this.getClass().getName());
			logger.debug("-----------------------------------------------------------------------------------------------------");
		}
	}

	//---------------------------------------------------------------------------------------------------

	public interface X {};

	declare parents:
		(org.quartz.Job+ && !org.quartz.Job) implements X;

	declare parents:
		(org.parallelj.internal.kernel.KCall) implements X;

	private Throwable X.exceptionThrown = null;

	private ForEachListener X.forEachListener = null;

	public ForEachListener X.getForEachListener() {
		return forEachListener;
	}

	public void X.setForEachListener(ForEachListener forEachListener) {
		this.forEachListener = forEachListener;
	}

	private String X.restartedFireInstanceId = null;

	public String X.getRestartedFireInstanceId() {
		return restartedFireInstanceId;
	}

	public void X.setRestartedFireInstanceId(String restartedFireInstanceId) {
		this.restartedFireInstanceId = restartedFireInstanceId;
	}

	//---------------------------------------------------------------------------------------------------

	//JobExecutionContext --> Job
	before(X self, JobExecutionContext jobExecutionContext):
		execution(public void Job+.execute(..) throws JobExecutionException)
		&& this(self)
		&& args(jobExecutionContext) {

		self.setRestartedFireInstanceId(jobExecutionContext.getJobDetail().getJobDataMap().getString(TrackNRestartPluginAll.RESTARTED_FIRE_INSTANCE_ID));
		self.setForEachListener((ForEachListener) jobExecutionContext.getJobDetail().getJobDataMap().get(TrackNRestartPluginAll.FOR_EACH_LISTENER));

		this.root = self;
	}

	after(X self, JobExecutionContext jobExecutionContext):
		execution(public void Job+.execute(..) throws JobExecutionException)
		&& this(self)
		&& args(jobExecutionContext) {

		Object oResult = jobExecutionContext.getResult();
		if (oResult instanceof JobDataMap){
			JobDataMap resultAsJobDataMap = (JobDataMap) oResult;
			resultAsJobDataMap.put(RETURN_CODE, this.result);
		} else {
			jobExecutionContext.setResult(this.result);
		}
	}

	after(X self):
		execution(protected KCall.new(..)) && this(self) {

		self.setRestartedFireInstanceId(root.getRestartedFireInstanceId());
		self.setForEachListener(root.getForEachListener());
	}

	after(X self):
		execution(protected KProcess.new(..)) && this(self) {

		self.setRestartedFireInstanceId(root.getRestartedFireInstanceId());
		self.setForEachListener(root.getForEachListener());
	}

    pointcut enter(KCall _kCall): call(* org.parallelj.internal.kernel.callback.Entry+.enter(KCall)) && args(_kCall);
    pointcut invoke(): call(public Object Method.invoke(Object, ..)) && !within(QuartzContextAdapter);

// Runnable program. ----------------------------------------------------------------------------------------------------------------------------------

    public interface Y {};

  	private Object Y.context = null;

  	public Object Y.getContext() {
  		return context;
  	}

  	public void Y.setContext(Object context) {
  		this.context=context;
  	}

    declare parents:
		(java.lang.Runnable+ ) implements X, Y;

    // 1) Transfert from RunnableCall to Runnable.
  	after(Object self) returning (Runnable ret):
    	 execution(* org.parallelj.internal.kernel.procedure.RunnableProcedure.RunnableCall.toRunnable())
      && this(self)  {

    	 ((X)ret).setForEachListener(((X)self).getForEachListener());
    	 ((X)ret).setRestartedFireInstanceId(((X)self).getRestartedFireInstanceId());
    	 ((Y)ret).setContext(((org.parallelj.internal.kernel.KCall)self).getContext());
    }

     // 2) Interception around call of run.
     void around(Object self): call(* run(..))
     && within(org.parallelj.internal.kernel.procedure.RunnableProcedure.RunnableCall)
     && within(Runnable+)  && this(self)  {

    	 String restartedFireInstanceId = ((X) self).getRestartedFireInstanceId();
    	 ForEachListener forEachListener = ((X)self).getForEachListener();
    	 Object program =  ((Y)self).getContext();

    	 if (isToProcess(restartedFireInstanceId,forEachListener,program)) {
    		try {
    			proceed(self);
     			if (isTrackNRestartAnnoted(program)) {
     				String oid = getOID(program);
   					track(forEachListener, oid, true);
     			}
    		} catch (Exception e) {
 				if (isTrackNRestartAnnoted(program)){
 					String oid = getOID(program);
 					if (isTrackNRestartExceptionPermitted(program, e)) {
 						this.result = ReturnCodes.FAILURE.name();
	 					track(forEachListener, oid, false);
					} else {
						abortAbruptly(new TrackNRestartException("Exception thrown ("+e.getClass().getName()+") is not in list of permitted exceptions "+filteredExceptionsAsString(getTrackNRestartAnnotedException(program))));
					}
 				}
    		}
     	} else {
     		if (isTrackNRestartAnnoted(program)) {
 				String oid = getOID(program);
 				track(forEachListener, oid, true);
			} else {
				abortAbruptly(new TrackNRestartException("Unable to get iteration OID while 'tracking' context."));
 			}
     	}
     }

     /**
      * Track the execution (success or failure).
      * @param forEachListener listener handling Quartz backend.
      * @param oid unique identifier
      * @param success boolean indicating if processing is a success or a failure.
      */
     private void track(ForEachListener forEachListener, String oid, boolean success) {
	     try {
				forEachListener.forEachInstanceComplete(oid, success);
	     } catch (JobPersistenceException e) {
				abortAbruptly(new TrackNRestartException("Unable to read status for iteration '"+ oid + "'.", e));
		} catch (SQLException e) {
				abortAbruptly(new TrackNRestartException("Unable to read status for iteration '"+ oid + "'.", e));
		}
     }

// restart part ----------------------------------------------------------------------------------------------------------------------------------------

     Object around(Object oo, KCall _kCall)  :
        invoke() && args(oo, ..) && cflow(enter(_kCall)) {

    	if (isToProcess( ((X) _kCall).getRestartedFireInstanceId(),((X) _kCall).getForEachListener(), oo)) {
               return proceed(oo, _kCall);
        } else {
               return null;
        }
     }

    /**
     * Check if an iterable must be processed. This means if the iterable is not already processed or the element was processed in error.
     * @param restartedFireInstanceId id of the batch to be restarted.
     * @param forEachListener listener handling request to Quartz.
     * @param program instance of the program processing the iterable.
     * @return true iterable has to be processed, false else.
     */
    private boolean isToProcess(String restartedFireInstanceId,  ForEachListener forEachListener, Object program)   {
		boolean result = true;
		if (program != null) {
			if (program.getClass().isAnnotationPresent(TrackNRestart.class)) {
				String oid = getOID(program);
				if (oid != null) {
					if (restartedFireInstanceId != null) {
						// logger.debug("Processing in restarting mode for oid : ["
						// + oid + "] ,program : [" + program +
						// "] ,instanceId :[" +instanceId+ "]");
						try {
							result = !forEachListener.isForEachInstanceIgnorable(restartedFireInstanceId, oid);
							// logger.debug(result ?
							// "This OID has not been already processed or" +
							// " already processed in error, so its processing is not skipped":
							// "This OID ("+oid+") has been already successfully processed, so its processing is skipped");
						} catch (JobPersistenceException e) {
							abortAbruptly(new TrackNRestartException("Unable to read status for iteration '"+ oid + "'.", e));
						} catch (SQLException e) {
							abortAbruptly(new TrackNRestartException("Unable to read status for iteration '"+ oid + "'.", e));
						}
					} else {
						// logger.debug("Processing in normal mode (ie not restarting mode) for : ["
						// + oid + "] ,program : [" + program + "]");
					}
				} else {
					abortAbruptly(new TrackNRestartException("Unable to get iteration OID while 'restarting' context."));
				}
			}
		}
		return result;
	}

    /**
     * Get unique identifier returned by the program (the program contains value to be processed).
     * @param program
     * @return unique identifier.
     */
	private String getOID(Object program) {
		String result = null;
		try {
			Method m = program.getClass().getDeclaredMethod("getOID", new Class[] {});
			return (String) m.invoke(program, new Object[] {});
		} catch (SecurityException e) {
			abortAbruptly(new TrackNRestartException(e));
		} catch (NoSuchMethodException e) {
			abortAbruptly(new TrackNRestartException("No getOID() method found while program " + program + " was annotated @TrackNRestart.", e));
		} catch (IllegalArgumentException e) {
			abortAbruptly(new TrackNRestartException(e));
		} catch (IllegalAccessException e) {
			abortAbruptly(new TrackNRestartException(e));
		} catch (InvocationTargetException e) {
			abortAbruptly(new TrackNRestartException(e));
		}

		return result;
	}

	/**
	 * Indicate of the Program contains annotation TrackNRestart.
	 * @param program
	 * @return true if program is annoted.
	 */
	private boolean isTrackNRestartAnnoted(Object program) {
		return program.getClass().isAnnotationPresent(TrackNRestart.class);
	}

	/**
	 * Check if the raised exception belong to the allowed ones in TrackNRestart annotation.
	 * @param program
	 * @param exception raised
	 * @return true if exception is allowed, false else.
	 */
	private boolean isTrackNRestartExceptionPermitted(Object program, Exception exception) {
		boolean result = false;
		Class<? extends Throwable>[] filteredExceptions =  getTrackNRestartAnnotedException(program);
		for (int i = 0; i < filteredExceptions.length; i++) {
				if (filteredExceptions[i].isAssignableFrom(exception.getClass())) {
					return true;
			}
		}
		return false;
	}

	/**
	 * Return exceptions defined in TrackNRestart annotation.
	 * @param program
	 * @return array of exceptions
	 */
	private Class<? extends Throwable>[] getTrackNRestartAnnotedException (Object program) {
		return program.getClass().getAnnotation(TrackNRestart.class).filteredExceptions();
	}

// tracking part ----------------------------------------------------------------------------------------------------------------------------------------
   after(Object oo, KCall _kCall) returning :
    	invoke() && args(oo, ..) && cflow(enter(_kCall)) {

		track(_kCall, oo, true, null);
    }

    after(Object oo, KCall _kCall) throwing (InvocationTargetException ite) :
    	invoke() && args(oo, ..) && cflow(enter(_kCall)) {

		this.result = ReturnCodes.FAILURE.name();
		track(_kCall, oo, false, ite);
    }

	private void track(KCall _kCall, Object program, boolean success,
			InvocationTargetException ite) {
		String oid = null;

		try {
			if (program != null) {
				Class<? extends Object> clazz = program.getClass();
				if (clazz.isAnnotationPresent(TrackNRestart.class)) {
					oid = getOID(program);
					if (oid != null) {
						if (success == true) {
							persist(_kCall, success, oid);
						} else {
							Class<? extends Throwable>[] filteredExceptions = clazz
									.getAnnotation(TrackNRestart.class)
									.filteredExceptions();
							if (isIteCausePermittedInList(ite,
									filteredExceptions)) {
								persist(_kCall, success, oid);
							} else {
								abortAbruptly(
										new TrackNRestartException(
												"Exception thrown ("
												+ ite.getCause().getClass().getName()
												+ ") is not in list of permitted exceptions "
												+ filteredExceptionsAsString(filteredExceptions)));
							}
						}
						//if (logger.isDebugEnabled()) {
							// logger.debug("oid='" + oid +
							// "' persisted with status='" + (success ?
							// ReturnCodes.SUCCESS : ReturnCodes.FAILURE) + "' "+ (ite ==
							// null ? "" : ", cause : " + ite.getCause()));
						//}
					} else {
						abortAbruptly(new TrackNRestartException("Unable to get iteration OID while 'tracking' context."));
					}
				}
			}
		} catch (JobPersistenceException e) {
			abortAbruptly(new TrackNRestartException("Unable to persist status ['" + (success ? ReturnCodes.SUCCESS : ReturnCodes.FAILURE) + "'] for iteration '" + oid + "'.", e));
		} catch (SQLException e) {
			abortAbruptly(new TrackNRestartException("Unable to persist status ['" + (success ? ReturnCodes.SUCCESS : ReturnCodes.FAILURE) + "'] for iteration '" + oid + "'.", e));
		}
	}

	private boolean isIteCausePermittedInList(InvocationTargetException ite,
			Class<? extends Throwable>[] filteredExceptions) {
		for (int i = 0; i < filteredExceptions.length; i++) {
			if (filteredExceptions[i].isAssignableFrom(ite.getCause()
					.getClass()))
				return true;
		}
		return false;
	}

	private String filteredExceptionsAsString(
			Class<? extends Throwable>[] filteredExceptions) {
		StringBuffer sb = new StringBuffer();
		sb.append("[");
		for (int i = 0; i < filteredExceptions.length; i++) {
			if (i > 0)
				sb.append(", ");
			sb.append(filteredExceptions[i].getName());
		}
		sb.append("]");
		return sb.toString();
	}

	private void persist(KCall _kCall, boolean success, String oid)
			throws JobPersistenceException, SQLException {
		((X) _kCall).getForEachListener().forEachInstanceComplete(oid, success);
	}

	private void abortAbruptly(Throwable t) {
		this.result = "ABORTED";
		Programs.as((Adapter) this.root).abort();
		this.root.exceptionThrown = t;
	}

}

package org.parallelj.tracknrestart.aspects;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.SQLException;

import org.apache.log4j.Logger;
import org.parallelj.Programs;
import org.parallelj.Programs.ProcessHelper;
import org.parallelj.internal.kernel.KCall;
import org.parallelj.internal.kernel.KProcess;
import org.parallelj.internal.reflect.ProgramAdapter.Adapter;
import org.parallelj.tracknrestart.ReturnCodes;
import org.parallelj.tracknrestart.annotations.TrackNRestart;
import org.parallelj.tracknrestart.databinding.ProgramFieldsBinder;
import org.parallelj.tracknrestart.listeners.ForEachListener;
import org.parallelj.tracknrestart.plugins.TrackNRestartPluginAll;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobPersistenceException;

privileged public aspect QuartzContextAdapter percflow (execution(public void Job+.execute(..) throws JobExecutionException)) {

	public static final String RETURN_CODE = "RETURN_CODE";

	declare precedence :
		org.parallelj.tracknrestart.aspects.QuartzContextAdapter,
		org.parallelj.launching.quartz.JobsAdapter;

	static Logger logger = Logger.getLogger("org.parallelj.tracknrestart");

	private X root;

	String result = ReturnCodes.SUCCESS.name();

//	Throwable exceptionThrown = null;

	//---------------------------------------------------------------------------------------------------

	declare parents:
		(@org.parallelj.launching.QuartzExecution *) implements Job;

	public void Job.execute(JobExecutionContext context) throws JobExecutionException {

		try {
			logger.debug("-----------------------------------------------------------------------------------------------------");
			logger.debug("STARTING //J Root Program "+this.getClass().getName());

			ProcessHelper<?> p = null;
//			context.setResult(new String());
//			context.setResult(new HashMap<String, Serializable>());
			context.setResult(new JobDataMap());
//			ProgramFieldsBinder.setProgramInputFields(this, context);
			p = Programs.as((Adapter) this).execute().join();
			ProgramFieldsBinder.getProgramOutputFields(this, context);

			X xx = (X)this;
			if (xx.exceptionThrown != null){
				throw new JobExecutionException(xx.exceptionThrown);
			}
		} catch (IllegalAccessException e) {
			throw new JobExecutionException(e);
		} catch (NoSuchFieldException e) {
			throw new JobExecutionException(e);
		} finally {
			logger.debug("ENDING   //J Root Program "+this.getClass().getName());
			logger.debug("-----------------------------------------------------------------------------------------------------");
		}
//		context.setResult(String.valueOf(p.getState()));
	}

	//---------------------------------------------------------------------------------------------------

	public interface X {};

	declare parents:
		(org.quartz.Job+ && !org.quartz.Job) implements X;

	declare parents:
		(org.parallelj.internal.kernel.KCall) implements X;

	private Throwable X.exceptionThrown = null;

	private ForEachListener X.forEachListener = null;

	public ForEachListener X.getForEachListener() {
		return forEachListener;
	}

	public void X.setForEachListener(ForEachListener forEachListener) {
		this.forEachListener = forEachListener;
	}

	private String X.restartedFireInstanceId = null;

	public String X.getRestartedFireInstanceId() {
		return restartedFireInstanceId;
	}

	public void X.setRestartedFireInstanceId(String restartedFireInstanceId) {
		this.restartedFireInstanceId = restartedFireInstanceId;
	}

	//---------------------------------------------------------------------------------------------------

	//JobExecutionContext --> Job
	before(X self, JobExecutionContext jobExecutionContext):
		execution(public void Job+.execute(..) throws JobExecutionException)
		&& this(self)
		&& args(jobExecutionContext) {

		//logger.debug("ASPECT BEFORE:execution(public void Job+.execute(..) throws JobExecutionException) && this(self) && args(jobExecutionContext)");

		self.setRestartedFireInstanceId(jobExecutionContext.getJobDetail().getJobDataMap().getString(TrackNRestartPluginAll.RESTARTED_FIRE_INSTANCE_ID));
		self.setForEachListener((ForEachListener) jobExecutionContext.getJobDetail().getJobDataMap().get(TrackNRestartPluginAll.FOR_EACH_LISTENER));
		//logger.debug("job="+self);
		//logger.debug("restartedFireInstanceId="+self.getRestartedFireInstanceId());
		//logger.debug("forEachListener="+self.getForEachListener());

		this.root = self;
	}

	after(X self, JobExecutionContext jobExecutionContext):
		execution(public void Job+.execute(..) throws JobExecutionException)
		&& this(self)
		&& args(jobExecutionContext) {

		//logger.debug("ASPECT AFTER:execution(public void Job+.execute(..) throws JobExecutionException) && this(self) && args(jobExecutionContext)");

		Object oResult = jobExecutionContext.getResult();
		if (oResult instanceof JobDataMap){
			JobDataMap resultAsJobDataMap = (JobDataMap) oResult;
			resultAsJobDataMap.put(RETURN_CODE, this.result);
		} else {
			jobExecutionContext.setResult(this.result);
		}
	}

	after(X self):
		execution(protected KCall.new(..)) && this(self) {

		//logger.debug("ASPECT AFTER:protected execution(protected KCall.new(..)) && this(self)");
		//logger.debug("self="+self);
		self.setRestartedFireInstanceId(root.getRestartedFireInstanceId());
		self.setForEachListener(root.getForEachListener());
		//logger.debug("restartedFireInstanceId="+self.getRestartedFireInstanceId());
		//logger.debug("forEachListener="+self.getForEachListener());
	}

	after(X self):
		execution(protected KProcess.new(..)) && this(self) {

		//logger.debug("ASPECT AFTER:protected execution(protected KProcess.new(..)) && this(self)");
		//logger.debug("self="+self);
		self.setRestartedFireInstanceId(root.getRestartedFireInstanceId());
		self.setForEachListener(root.getForEachListener());
		//logger.debug("restartedFireInstanceId="+self.getRestartedFireInstanceId());
		//logger.debug("forEachListener="+self.getForEachListener());
	}

    pointcut enter(KCall _kCall): call(* org.parallelj.internal.kernel.callback.Entry+.enter(KCall)) && args(_kCall);
    pointcut invoke(): call(public Object Method.invoke(Object, ..)) && !within(QuartzContextAdapter);

// Runnable program. ----------------------------------------------------------------------------------------------------------------------------------

    public interface Y {};

  	private Object Y.context = null;

  	public Object Y.getContext() {
  		return context;
  	}

  	public void Y.setContext(Object context) {
  		this.context=context;
  	}

    declare parents:
		(java.lang.Runnable+ ) implements X, Y;

    // 1) Transfert from RunnableCall to Runnable.
  	after(Object self) returning (Runnable ret):
    	 execution(* org.parallelj.internal.kernel.procedure.RunnableProcedure.RunnableCall.toRunnable())
      && this(self)  {

    	 ((X)ret).setForEachListener(((X)self).getForEachListener());
    	 ((X)ret).setRestartedFireInstanceId(((X)self).getRestartedFireInstanceId());
    	 ((Y)ret).setContext(((org.parallelj.internal.kernel.KCall)self).getContext());
    }

     // 2) Interception around call of run.
     void around(Object self): call(* run(..))
     && within(org.parallelj.internal.kernel.procedure.RunnableProcedure.RunnableCall)
     && within(Runnable+)  && this(self)  {

    	 String restartedFireInstanceId = ((X) self).getRestartedFireInstanceId();
    	 ForEachListener forEachListener = ((X)self).getForEachListener();
    	 Object program =  ((Y)self).getContext();

    	 if (isToProcess(restartedFireInstanceId,forEachListener,program)) {
    		try {
    			proceed(self);
     			if (isTrackNRestartAnnoted(program)) {
     				String oid = getOID(program);
   					track(forEachListener, oid, true);
     			}
    		} catch (Exception e) {
 				if (isTrackNRestartAnnoted(program)){
 					String oid = getOID(program);
 					if (isTrackNRestartExceptionPermitted(program, e)) {
 						this.result = ReturnCodes.FAILURE.name();
	 					track(forEachListener, oid, false);
					} else {
						abortAbruptly(new TrackNRestartException("Exception thrown ("+e.getClass().getName()+") is not in list of permitted exceptions "+filteredExceptionsAsString(getTrackNRestartAnnotedException(program))));
					}
 				}
    		}
     	} else {
     		if (isTrackNRestartAnnoted(program)) {
 				String oid = getOID(program);
 				track(forEachListener, oid, true);
			} else {
				abortAbruptly(new TrackNRestartException("Unable to get iteration OID while 'tracking' context."));
 			}
     	}
     }

     /**
      * Track the execution (success or failure).
      * @param forEachListener listener handling Quartz backend.
      * @param oid unique identifier
      * @param success boolean indicating if processing is a success or a failure.
      */
     private void track(ForEachListener forEachListener, String oid, boolean success) {
	     try {
				forEachListener.forEachInstanceComplete(oid, success);
	     } catch (JobPersistenceException e) {
				abortAbruptly(new TrackNRestartException(e));
//				throw new TrackNRestartException("Unable to read status for iteration '"+ oid + "'.", e);
		} catch (SQLException e) {
				abortAbruptly(new TrackNRestartException(e));
//				throw new TrackNRestartException("Unable to read status for iteration '"+ oid + "'.", e);
		}
     }

// restart part ----------------------------------------------------------------------------------------------------------------------------------------

     Object around(Object oo, KCall _kCall)  :
        invoke() && args(oo, ..) && cflow(enter(_kCall)) {

    	if (isToProcess( ((X) _kCall).getRestartedFireInstanceId(),((X) _kCall).getForEachListener(), oo)) {
               return proceed(oo, _kCall);
        } else {
               return null;
        }
     }

    /**
     * Check if an iterable must be processed. This means if the iterable is not already processed or the element was processed in error.
     * @param restartedFireInstanceId id of the batch to be restarted.
     * @param forEachListener listener handling request to Quartz.
     * @param program instance of the program processing the iterable.
     * @return true iterable has to be processed, false else.
     */
    private boolean isToProcess(String restartedFireInstanceId,  ForEachListener forEachListener, Object program)   {
		boolean result = true;
		if (program != null) {
			if (program.getClass().isAnnotationPresent(TrackNRestart.class)) {
				String oid = getOID(program);
				if (oid != null) {
					if (restartedFireInstanceId != null) {
						// logger.debug("Processing in restarting mode for oid : ["
						// + oid + "] ,program : [" + program +
						// "] ,instanceId :[" +instanceId+ "]");
						try {
							result = !forEachListener.isForEachInstanceIgnorable(restartedFireInstanceId, oid);
							// logger.debug(result ?
							// "This OID has not been already processed or" +
							// " already processed in error, so its processing is not skipped":
							// "This OID ("+oid+") has been already successfully processed, so its processing is skipped");
						} catch (JobPersistenceException e) {
							abortAbruptly(new TrackNRestartException(e));
//							throw new TrackNRestartException(
//									"Unable to read status for iteration '"
//											+ oid + "'.", e);
						} catch (SQLException e) {
							abortAbruptly(new TrackNRestartException(e));
//							throw new TrackNRestartException(
//									"Unable to read status for iteration '"
//											+ oid + "'.", e);
						}
					} else {
						// logger.debug("Processing in normal mode (ie not restarting mode) for : ["
						// + oid + "] ,program : [" + program + "]");
					}
				} else {
					abortAbruptly(new TrackNRestartException("Unable to get iteration OID while 'restarting' context."));
				}
			}
		}
		return result;
	}

    /**
     * Get unique identifier returned by the program (the program contains value to be processed).
     * @param program
     * @return unique identifier.
     */
	private String getOID(Object program) {
		String result = null;
		try {
			Method m = program.getClass().getDeclaredMethod("getOID", new Class[] {});
			return (String) m.invoke(program, new Object[] {});
		} catch (SecurityException e) {
			abortAbruptly(new TrackNRestartException(e));
		} catch (NoSuchMethodException e) {
			abortAbruptly(e);
//			throw new TrackNRestartException(
//					"No getOID() method found while program " + program
//							+ " was annotated @TrackNRestart.", e1);
		} catch (IllegalArgumentException e) {
			abortAbruptly(new TrackNRestartException(e));
		} catch (IllegalAccessException e) {
			abortAbruptly(new TrackNRestartException(e));
		} catch (InvocationTargetException e) {
			abortAbruptly(new TrackNRestartException(e));
		}

		return result;
	}

	/**
	 * Indicate of the Program contains annotation TrackNRestart.
	 * @param program
	 * @return true if program is annoted.
	 */
	private boolean isTrackNRestartAnnoted(Object program) {
		return program.getClass().isAnnotationPresent(TrackNRestart.class);
	}

	/**
	 * Check if the raised exception belong to the allowed ones in TrackNRestart annotation.
	 * @param program
	 * @param exception raised
	 * @return true if exception is allowed, false else.
	 */
	private boolean isTrackNRestartExceptionPermitted(Object program, Exception exception) {
		boolean result = false;
		Class<? extends Throwable>[] filteredExceptions =  getTrackNRestartAnnotedException(program);
		for (int i = 0; i < filteredExceptions.length; i++) {
				if (filteredExceptions[i].isAssignableFrom(exception.getClass())) {
					return true;
			}
		}
		return false;
	}

	/**
	 * Return exceptions defined in TrackNRestart annotation.
	 * @param program
	 * @return array of exceptions
	 */
	private Class<? extends Throwable>[] getTrackNRestartAnnotedException (Object program) {
		return program.getClass().getAnnotation(TrackNRestart.class).filteredExceptions();
	}

// tracking part ----------------------------------------------------------------------------------------------------------------------------------------
   after(Object oo, KCall _kCall) returning :
    	invoke() && args(oo, ..) && cflow(enter(_kCall)) {

		//logger.debug("ASPECT AFTER RETURNING");
		//logger.debug("_KCall="+_kCall);
		track(_kCall, oo, true, null);
    }

    after(Object oo, KCall _kCall) throwing (InvocationTargetException ite) :
    	invoke() && args(oo, ..) && cflow(enter(_kCall)) {

		//logger.debug("ASPECT AFTER THROWING (InvocationTargetException)");
		//logger.debug("_KCall="+_kCall);
		this.result = ReturnCodes.FAILURE.name();
		track(_kCall, oo, false, ite);
    }

	private void track(KCall _kCall, Object program, boolean success,
			InvocationTargetException ite) {
		String oid = null;

		try {
			if (program != null) {
				Class<? extends Object> clazz = program.getClass();
				if (clazz.isAnnotationPresent(TrackNRestart.class)) {
					oid = getOID(program);
					if (oid != null) {
						if (success == true) {
							persist(_kCall, success, oid);
						} else {
							Class<? extends Throwable>[] filteredExceptions = clazz
									.getAnnotation(TrackNRestart.class)
									.filteredExceptions();
							if (isIteCausePermittedInList(ite,
									filteredExceptions)) {
								persist(_kCall, success, oid);
							} else {
								abortAbruptly(
										new TrackNRestartException(
												"Exception thrown ("
												+ ite.getCause().getClass().getName()
												+ ") is not in list of permitted exceptions "
												+ filteredExceptionsAsString(filteredExceptions)));
							}
						}
						//if (logger.isDebugEnabled()) {
							// logger.debug("oid='" + oid +
							// "' persisted with status='" + (success ?
							// ReturnCodes.SUCCESS : ReturnCodes.FAILURE) + "' "+ (ite ==
							// null ? "" : ", cause : " + ite.getCause()));
						//}
					} else {
						abortAbruptly(new TrackNRestartException("Unable to get iteration OID while 'tracking' context."));
					}
				}
			}
		} catch (JobPersistenceException e) {
			abortAbruptly(new TrackNRestartException(e));
//			throw new TrackNRestartException("Unable to persist status ['"
//					+ (success ? ReturnCodes.SUCCESS : ReturnCodes.FAILURE)
//					+ "'] for iteration '" + oid + "'.", e);
		} catch (SQLException e) {
			abortAbruptly(new TrackNRestartException(e));
//			throw new TrackNRestartException("Unable to persist status ['"
//					+ (success ? ReturnCodes.SUCCESS : ReturnCodes.FAILURE)
//					+ "'] for iteration '" + oid + "'.", e);
		}
	}

	private boolean isIteCausePermittedInList(InvocationTargetException ite,
			Class<? extends Throwable>[] filteredExceptions) {
		for (int i = 0; i < filteredExceptions.length; i++) {
			if (filteredExceptions[i].isAssignableFrom(ite.getCause()
					.getClass()))
				return true;
		}
		return false;
	}

	private String filteredExceptionsAsString(
			Class<? extends Throwable>[] filteredExceptions) {
		StringBuffer sb = new StringBuffer();
		sb.append("[");
		for (int i = 0; i < filteredExceptions.length; i++) {
			if (i > 0)
				sb.append(", ");
			sb.append(filteredExceptions[i].getName());
		}
		sb.append("]");
		return sb.toString();
	}

	private void persist(KCall _kCall, boolean success, String oid)
			throws JobPersistenceException, SQLException {
		((X) _kCall).getForEachListener().forEachInstanceComplete(oid, success);
	}

	private void abortAbruptly(Throwable t) {
		this.result = "ABORTED";
		Programs.as((Adapter) this.root).abort();
		this.root.exceptionThrown = t;
	}

}


XWIKI-10321: Limit the impact on some APIs

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package com.xpn.xwiki.util;

import java.io.Reader;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.util.List;
import org.xwiki.security.authorization.ContextualAuthorizationManager;
import org.xwiki.security.authorization.Right;

import com.xpn.xwiki.XWiki;
import com.xpn.xwiki.web.Utils;
import org.apache.oro.text.regex.MalformedPatternException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

/**
 * Add a backward compatibility layer to the {@link com.xpn.xwiki.util.Util} class.
 *
 * @version $Id$
 */
public privileged aspect UtilCompatibilityAspect
{
    /**
     * @deprecated use {@link #getUniqueMatches(String, String, int)} instead
     */
    @Deprecated
    public List<String> Util.getMatches(String content, String spattern, int group)
        throws MalformedPatternException
    {
        return this.getUniqueMatches(content, spattern, group);
    }

    /** @deprecated Use {@link org.apache.commons.io.FileUtils#readFileToString(File, String)} */
    @Deprecated
    public static String Util.getFileContent(File file) throws IOException
    {
        ContextualAuthorizationManager authorization = Utils.getComponent(ContextualAuthorizationManager.class);
        if (!authorization.hasAccess(Right.PROGRAM)) {
            throw new IOException("Access denied.");
        }
        return FileUtils.readFileToString(file, XWiki.DEFAULT_ENCODING);
    }

    /** @deprecated Use {@link org.apache.commons.io.IOUtils#toString(Reader)} */
    @Deprecated
    public static String Util.getFileContent(Reader reader) throws IOException
    {
        ContextualAuthorizationManager authorization = Utils.getComponent(ContextualAuthorizationManager.class);
        if (!authorization.hasAccess(Right.PROGRAM)) {
            throw new IOException("Access denied.");
        }
        return IOUtils.toString(reader);
    }

    /** @deprecated Use {@link org.apache.commons.io.FileUtils#readFileToByteArray(File)} */
    @Deprecated
    public static byte[] Util.getFileContentAsBytes(File file) throws IOException
    {
        ContextualAuthorizationManager authorization = Utils.getComponent(ContextualAuthorizationManager.class);
        if (!authorization.hasAccess(Right.PROGRAM)) {
            throw new IOException("Access denied.");
        }
        return FileUtils.readFileToByteArray(file);
    }

    /** @deprecated Use {@link org.apache.commons.io.IOUtils#toByteArray(InputStream)} */
    @Deprecated
    public static byte[] Util.getFileContentAsBytes(InputStream is) throws IOException
    {
        ContextualAuthorizationManager authorization = Utils.getComponent(ContextualAuthorizationManager.class);
        if (!authorization.hasAccess(Right.PROGRAM)) {
            throw new IOException("Access denied.");
        }
        return IOUtils.toByteArray(is);
    }
}

/*
 * See the NOTICE file distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package com.xpn.xwiki.util;

import java.io.Reader;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.util.List;

import com.xpn.xwiki.XWiki;
import org.apache.oro.text.regex.MalformedPatternException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

/**
 * Add a backward compatibility layer to the {@link com.xpn.xwiki.util.Util} class.
 *
 * @version $Id$
 */
public privileged aspect UtilCompatibilityAspect
{
    /**
     * @deprecated use {@link #getUniqueMatches(String, String, int)} instead
     */
    @Deprecated
    public List<String> Util.getMatches(String content, String spattern, int group)
        throws MalformedPatternException
    {
        return this.getUniqueMatches(content, spattern, group);
    }

    /** @deprecated Use {@link org.apache.commons.io.FileUtils#readFileToString(File, String)} */
    @Deprecated
    public static String Util.getFileContent(File file) throws IOException
    {
        return FileUtils.readFileToString(file, XWiki.DEFAULT_ENCODING);
    }

    /** @deprecated Use {@link org.apache.commons.io.IOUtils#toString(Reader)} */
    @Deprecated
    public static String Util.getFileContent(Reader reader) throws IOException
    {
        return IOUtils.toString(reader);
    }

    /** @deprecated Use {@link org.apache.commons.io.FileUtils#readFileToByteArray(File)} */
    @Deprecated
    public static byte[] Util.getFileContentAsBytes(File file) throws IOException
    {
        return FileUtils.readFileToByteArray(file);
    }

    /** @deprecated Use {@link org.apache.commons.io.IOUtils#toByteArray(InputStream)} */
    @Deprecated
    public static byte[] Util.getFileContentAsBytes(InputStream is) throws IOException
    {
        return IOUtils.toByteArray(is);
    }
}


*Event id fixed for unlock event

*Event id fixed for unlock event

package benchmark.competition.java.jUnitRV_MMT.monitoring;

import java.util.concurrent.locks.Lock;

import monitoring.impl.MonitorFactory;
import monitoring.intf.Monitor;
import properties.Property;
import properties.competition.JavaRV_mmt;
import structure.impl.other.Verdict;
import structure.intf.QEA;
import benchmark.competition.java.jUnitRV_MMT.ExampleLocking;

public aspect JUnitRV_MMTThreeAspect {

	private final Monitor monitor;

	private final int RUN = 1;
	private final int LOCK_TRUE = 2;
	private final int UNLOCK = 3;
	private final int ACTION = 4;

	public JUnitRV_MMTThreeAspect() {
		QEA qea = new JavaRV_mmt().make(Property.JAVARV_MMT_THREE);
		monitor = MonitorFactory.create(qea);
	}

	pointcut run(ExampleLocking thread) : execution(void ExampleLocking.run())
		&& target(thread);

	pointcut action(ExampleLocking thread) : call(void ExampleLocking.action())
		&& target(thread);

	before(ExampleLocking thread) : run(thread) {
		Verdict verdict = null;
		synchronized (monitor) {
			verdict = monitor.step(RUN, thread);
		}
		if (verdict == Verdict.FAILURE || verdict == Verdict.WEAK_FAILURE) {
			System.err
					.println("Violation in JUnitRV (MMT) 3. [thread="
							+ thread
							+ "]. - A thread has to call lock (returning true) to acquire the lock before it may call action - A call to lock only returns true, if no thread is currently holding the lock.");
		}
	};

	boolean around(Lock lockObj, ExampleLocking thread) :
			call(boolean Lock.tryLock())
			&& cflow(call(boolean ExampleLocking.lock())) && target(lockObj)
			&& this(thread) {

		boolean result = false;
		// We perform the lock and the monitor step as an atomic action
		// i.e. whilst holding the lockObj, so that we cannot be interrupted
		synchronized (lockObj) {
			result = proceed(lockObj, thread);
			if (result) {
				Verdict verdict = null;
				// Ordering between lockObj and monitor important
				synchronized (monitor) {
					verdict = monitor.step(LOCK_TRUE, thread);
				}
				if (verdict == Verdict.FAILURE || verdict == Verdict.WEAK_FAILURE) {
					System.err
							.println("Violation in JUnitRV (MMT) 3. [thread="
									+ thread
									+ "]. - A thread has to call lock (returning true) to acquire the lock before it may call action - A call to lock only returns true, if no thread is currently holding the lock.");
				}

			}
		}
		return result;
	};

	void around(Lock lockObj, ExampleLocking thread) :
		call(void Lock.unlock()) && cflow(call(void ExampleLocking.unlock()))
		&& target(lockObj) && this(thread) {

		// We perform the unlock and the monitor step as an atomic action
		// i.e. whilst holding the lockObj, so that we cannot be interrupted
		Verdict verdict = null;
		synchronized (lockObj) {
			proceed(lockObj, thread);
			// Ordering between lockObj and monitor important
			synchronized (monitor) {
				verdict = monitor.step(UNLOCK, thread);
			}
		}
		if (verdict == Verdict.FAILURE || verdict == Verdict.WEAK_FAILURE) {
			System.err
					.println("Violation in JUnitRV (MMT) 3. [thread="
							+ thread
							+ "]. - A thread has to call lock (returning true) to acquire the lock before it may call action - A call to lock only returns true, if no thread is currently holding the lock.");
		}
	};

	before(ExampleLocking thread) : action(thread) {
		Verdict verdict = null;
		synchronized (monitor) {
			verdict = monitor.step(ACTION, thread);
		}
		if (verdict == Verdict.FAILURE || verdict == Verdict.WEAK_FAILURE) {
			System.err
					.println("Violation in JUnitRV (MMT) 3. [id="
							+ thread
							+ "]. - A thread has to call lock (returning true) to acquire the lock before it may call action - A call to lock only returns true, if no thread is currently holding the lock.");
		}
	};

}

package benchmark.competition.java.jUnitRV_MMT.monitoring;

import java.util.concurrent.locks.Lock;

import monitoring.impl.MonitorFactory;
import monitoring.intf.Monitor;
import properties.Property;
import properties.competition.JavaRV_mmt;
import structure.impl.other.Verdict;
import structure.intf.QEA;
import benchmark.competition.java.jUnitRV_MMT.ExampleLocking;

public aspect JUnitRV_MMTThreeAspect {

	private final Monitor monitor;

	private final int RUN = 1;
	private final int LOCK_TRUE = 2;
	private final int UNLOCK = 3;
	private final int ACTION = 4;

	public JUnitRV_MMTThreeAspect() {
		QEA qea = new JavaRV_mmt().make(Property.JAVARV_MMT_THREE);
		monitor = MonitorFactory.create(qea);
	}

	pointcut run(ExampleLocking thread) : execution(void ExampleLocking.run())
		&& target(thread);

	pointcut action(ExampleLocking thread) : call(void ExampleLocking.action())
		&& target(thread);

	before(ExampleLocking thread) : run(thread) {
		Verdict verdict = null;
		synchronized (monitor) {
			verdict = monitor.step(RUN, thread);
		}
		if (verdict == Verdict.FAILURE || verdict == Verdict.WEAK_FAILURE) {
			System.err
					.println("Violation in JUnitRV (MMT) 3. [thread="
							+ thread
							+ "]. - A thread has to call lock (returning true) to acquire the lock before it may call action - A call to lock only returns true, if no thread is currently holding the lock.");
		}
	};

	boolean around(Lock lockObj, ExampleLocking thread) :
			call(boolean Lock.tryLock())
			&& cflow(call(boolean ExampleLocking.lock())) && target(lockObj)
			&& this(thread) {

		boolean result = false;
		// We perform the lock and the monitor step as an atomic action
		// i.e. whilst holding the lockObj, so that we cannot be interrupted
		synchronized (lockObj) {
			result = proceed(lockObj, thread);
			if (result) {
				Verdict verdict = null;
				// Ordering between lockObj and monitor important
				synchronized (monitor) {
					verdict = monitor.step(LOCK_TRUE, thread);
				}
				if (verdict == Verdict.FAILURE || verdict == Verdict.WEAK_FAILURE) {
					System.err
							.println("Violation in JUnitRV (MMT) 3. [thread="
									+ thread
									+ "]. - A thread has to call lock (returning true) to acquire the lock before it may call action - A call to lock only returns true, if no thread is currently holding the lock.");
				}

			}
		}
		return result;
	};

	void around(Lock lockObj, ExampleLocking thread) :
		call(void Lock.unlock()) && cflow(call(void ExampleLocking.unlock()))
		&& target(lockObj) && this(thread) {

		// We perform the unlock and the monitor step as an atomic action
		// i.e. whilst holding the lockObj, so that we cannot be interrupted
		Verdict verdict = null;
		synchronized (lockObj) {
			proceed(lockObj, thread);
			// Ordering between lockObj and monitor important
			synchronized (monitor) {
				verdict = monitor.step(LOCK_TRUE, thread);
			}
		}
		if (verdict == Verdict.FAILURE || verdict == Verdict.WEAK_FAILURE) {
			System.err
					.println("Violation in JUnitRV (MMT) 3. [thread="
							+ thread
							+ "]. - A thread has to call lock (returning true) to acquire the lock before it may call action - A call to lock only returns true, if no thread is currently holding the lock.");
		}
	};

	before(ExampleLocking thread) : action(thread) {
		Verdict verdict = null;
		synchronized (monitor) {
			verdict = monitor.step(ACTION, thread);
		}
		if (verdict == Verdict.FAILURE || verdict == Verdict.WEAK_FAILURE) {
			System.err
					.println("Violation in JUnitRV (MMT) 3. [id="
							+ thread
							+ "]. - A thread has to call lock (returning true) to acquire the lock before it may call action - A call to lock only returns true, if no thread is currently holding the lock.");
		}
	};

}


Add @AjSuppressWarnings on static mocks

Add @AjSuppressWarnings on static mocks

Issue: SPR-12447

/*
 * Copyright 2002-2014 the original author or authors.
 *
 * 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 org.springframework.mock.staticmock;

import org.aspectj.lang.annotation.RequiredTypes;
import org.aspectj.lang.annotation.SuppressAjWarnings;

/**
 * Annotation-based aspect to use in test builds to enable mocking of static methods
 * on JPA-annotated {@code @Entity} classes, as used by Spring Roo for so-called
 * finder methods.
 *
 *
Mocking will occur within the call stack of any {@code public} method in a
 * class (typically a test class) that is annotated with {@code @MockStaticEntityMethods}.
 * Thus mocking is not limited to {@code @Test} methods. Furthermore, new mock
 * state will be created for the invocation of each such public method, even when
 * the method is invoked from another such public method.
 *
 *
This aspect also provides static methods to simplify the programming model for
 * setting expectations and entering playback mode.
 *
 *
Usage

 *

 *
Annotate a test class with {@code @MockStaticEntityMethods}.
 *
In each test method, the {@code AnnotationDrivenStaticEntityMockingControl}
 * will begin in recording mode.
 *
Invoke static methods on JPA-annotated {@code @Entity} classes, with each
 * recording-mode invocation being followed by an invocation of either
 * {@link #expectReturn(Object)} or {@link #expectThrow(Throwable)} on the
 * {@code AnnotationDrivenStaticEntityMockingControl}.
 *
Invoke the {@link #playback()} method.
 *
Call the code you wish to test that uses the static methods on the
 * JPA-annotated {@code @Entity} classes.
 *
Verification will occur automatically after the test method has executed
 * and returned. However, mock verification will not occur if the test method
 * throws an exception.
 *

 *
 *
Programmatic Control of the Mock

 *
For scenarios where it would be convenient to programmatically verify
 * the recorded expectations or reset the state of the mock, consider
 * using combinations of {@link #verify()} and {@link #reset()}.
 *
 * @author Rod Johnson
 * @author Ramnivas Laddad
 * @author Sam Brannen
 * @see MockStaticEntityMethods
 */
@RequiredTypes("javax.persistence.Entity")
public aspect AnnotationDrivenStaticEntityMockingControl extends AbstractMethodMockingControl {

	/**
	 * Expect the supplied {@link Object} to be returned by the previous static
	 * method invocation.
	 * @see #playback()
	 */
	public static void expectReturn(Object retVal) {
		AnnotationDrivenStaticEntityMockingControl.aspectOf().expectReturnInternal(retVal);
	}

	/**
	 * Expect the supplied {@link Throwable} to be thrown by the previous static
	 * method invocation.
	 * @see #playback()
	 */
	public static void expectThrow(Throwable throwable) {
		AnnotationDrivenStaticEntityMockingControl.aspectOf().expectThrowInternal(throwable);
	}

	/**
	 * Stop recording mock expectations and enter playback mode.
	 * @see #expectReturn(Object)
	 * @see #expectThrow(Throwable)
	 */
	public static void playback() {
		AnnotationDrivenStaticEntityMockingControl.aspectOf().playbackInternal();
	}

	/**
	 * Verify that all expectations have been fulfilled.
	 * @since 4.0.2
	 * @see #reset()
	 */
	public static void verify() {
		AnnotationDrivenStaticEntityMockingControl.aspectOf().verifyInternal();
	}

	/**
	 * Reset the state of the mock and enter recording mode.
	 * @since 4.0.2
	 * @see #verify()
	 */
	public static void reset() {
		AnnotationDrivenStaticEntityMockingControl.aspectOf().resetInternal();
	}

	// Apparently, the following pointcut was originally defined to only match
	// methods directly annotated with @Test (in order to allow methods in
	// @MockStaticEntityMethods classes to invoke each other without creating a
	// new mocking environment); however, this is no longer the case. The current
	// pointcut applies to all public methods in @MockStaticEntityMethods classes.
	@SuppressAjWarnings("adviceDidNotMatch")
	protected pointcut mockStaticsTestMethod() : execution(public * (@MockStaticEntityMethods *).*(..));

	@SuppressAjWarnings("adviceDidNotMatch")
	protected pointcut methodToMock() : execution(public static * (@javax.persistence.Entity *).*(..));

}

/*
 * Copyright 2002-2014 the original author or authors.
 *
 * 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 org.springframework.mock.staticmock;

import org.aspectj.lang.annotation.RequiredTypes;

/**
 * Annotation-based aspect to use in test builds to enable mocking of static methods
 * on JPA-annotated {@code @Entity} classes, as used by Spring Roo for so-called
 * finder methods.
 *
 *
Mocking will occur within the call stack of any {@code public} method in a
 * class (typically a test class) that is annotated with {@code @MockStaticEntityMethods}.
 * Thus mocking is not limited to {@code @Test} methods. Furthermore, new mock
 * state will be created for the invocation of each such public method, even when
 * the method is invoked from another such public method.
 *
 *
This aspect also provides static methods to simplify the programming model for
 * setting expectations and entering playback mode.
 *
 *
Usage

 *

 *
Annotate a test class with {@code @MockStaticEntityMethods}.
 *
In each test method, the {@code AnnotationDrivenStaticEntityMockingControl}
 * will begin in recording mode.
 *
Invoke static methods on JPA-annotated {@code @Entity} classes, with each
 * recording-mode invocation being followed by an invocation of either
 * {@link #expectReturn(Object)} or {@link #expectThrow(Throwable)} on the
 * {@code AnnotationDrivenStaticEntityMockingControl}.
 *
Invoke the {@link #playback()} method.
 *
Call the code you wish to test that uses the static methods on the
 * JPA-annotated {@code @Entity} classes.
 *
Verification will occur automatically after the test method has executed
 * and returned. However, mock verification will not occur if the test method
 * throws an exception.
 *

 *
 *
Programmatic Control of the Mock

 *
For scenarios where it would be convenient to programmatically verify
 * the recorded expectations or reset the state of the mock, consider
 * using combinations of {@link #verify()} and {@link #reset()}.
 *
 * @author Rod Johnson
 * @author Ramnivas Laddad
 * @author Sam Brannen
 * @see MockStaticEntityMethods
 */
@RequiredTypes("javax.persistence.Entity")
public aspect AnnotationDrivenStaticEntityMockingControl extends AbstractMethodMockingControl {

	/**
	 * Expect the supplied {@link Object} to be returned by the previous static
	 * method invocation.
	 * @see #playback()
	 */
	public static void expectReturn(Object retVal) {
		AnnotationDrivenStaticEntityMockingControl.aspectOf().expectReturnInternal(retVal);
	}

	/**
	 * Expect the supplied {@link Throwable} to be thrown by the previous static
	 * method invocation.
	 * @see #playback()
	 */
	public static void expectThrow(Throwable throwable) {
		AnnotationDrivenStaticEntityMockingControl.aspectOf().expectThrowInternal(throwable);
	}

	/**
	 * Stop recording mock expectations and enter playback mode.
	 * @see #expectReturn(Object)
	 * @see #expectThrow(Throwable)
	 */
	public static void playback() {
		AnnotationDrivenStaticEntityMockingControl.aspectOf().playbackInternal();
	}

	/**
	 * Verify that all expectations have been fulfilled.
	 * @since 4.0.2
	 * @see #reset()
	 */
	public static void verify() {
		AnnotationDrivenStaticEntityMockingControl.aspectOf().verifyInternal();
	}

	/**
	 * Reset the state of the mock and enter recording mode.
	 * @since 4.0.2
	 * @see #verify()
	 */
	public static void reset() {
		AnnotationDrivenStaticEntityMockingControl.aspectOf().resetInternal();
	}

	// Apparently, the following pointcut was originally defined to only match
	// methods directly annotated with @Test (in order to allow methods in
	// @MockStaticEntityMethods classes to invoke each other without creating a
	// new mocking environment); however, this is no longer the case. The current
	// pointcut applies to all public methods in @MockStaticEntityMethods classes.
	protected pointcut mockStaticsTestMethod() : execution(public * (@MockStaticEntityMethods *).*(..));

	protected pointcut methodToMock() : execution(public static * (@javax.persistence.Entity *).*(..));

}


Make pointcut pick up subclasses of ScalaJavaBuilder.

This fixes a problem at a customer (cleaning output folders, leading to
empty output folders after successful builds), but I could not reproduce
locally. Confirmed it works for said customer, so rolling back to main
project.

Fixed #1001995

(cherry picked from commit bcd85b3a3af3b00b3cb195b2561c1c880feac0b9)

/*
 * Copyright 2005-2010 LAMP/EPFL
 */
// $Id$

package scala.tools.eclipse.contribution.weaving.jdt.builderoptions;

import org.eclipse.core.resources.IResource;

import java.util.Map;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.internal.core.builder.BatchImageBuilder;
import org.eclipse.jdt.internal.core.builder.JavaBuilder;
//import org.eclipse.jdt.internal.core.builder.ClasspathMultiDirectory;
import org.eclipse.jdt.internal.core.util.Util;

@SuppressWarnings("restriction")
public privileged aspect ScalaJavaBuilderAspect {
  pointcut build() :
    execution(IProject[] ScalaJavaBuilder+.build(int, Map, IProgressMonitor) throws CoreException);

  pointcut cleanOutputFolders(boolean copyBack) :
    args(copyBack) &&
    execution(void BatchImageBuilder.cleanOutputFolders(boolean) throws CoreException);

  pointcut isJavaLikeFileName(String fileName) :
    args(fileName) &&
    execution(boolean Util.isJavaLikeFileName(String));

  pointcut filterExtraResource(IResource resource) :
    args(resource) &&
    execution(boolean JavaBuilder.filterExtraResource(IResource));

  void around(BatchImageBuilder builder, boolean copyBack) throws CoreException :
    target(builder) &&
    cleanOutputFolders(copyBack) &&
    cflow(build()) {
    // Suppress the cleaning behaviour but do the extra resource copying if requested
    if (copyBack)
      for (int i = 0, l = builder.sourceLocations.length; i < l; i++) {
        org.eclipse.jdt.internal.core.builder.ClasspathMultiDirectory sourceLocation = builder.sourceLocations[i];
        if (sourceLocation.hasIndependentOutputFolder)
          builder.copyExtraResourcesBack(sourceLocation, false);
      }
  }

  boolean around(String fileName) :
    isJavaLikeFileName(fileName) &&
    cflow(build()) &&
    !cflow(cleanOutputFolders(*)) {
    if (fileName != null && fileName.endsWith("scala"))
      return false;
    else
      return proceed(fileName);
  }

  boolean around(IResource resource) :
    filterExtraResource(resource) &&
    cflow(build()) {
    return resource.getName().endsWith(".scala") || proceed(resource);
  }
}

/*
 * Copyright 2005-2010 LAMP/EPFL
 */
// $Id$

package scala.tools.eclipse.contribution.weaving.jdt.builderoptions;

import org.eclipse.core.resources.IResource;

import java.util.Map;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.internal.core.builder.BatchImageBuilder;
import org.eclipse.jdt.internal.core.builder.JavaBuilder;
//import org.eclipse.jdt.internal.core.builder.ClasspathMultiDirectory;
import org.eclipse.jdt.internal.core.util.Util;

@SuppressWarnings("restriction")
public privileged aspect ScalaJavaBuilderAspect {
  pointcut build() :
    execution(IProject[] ScalaJavaBuilder.build(int, Map, IProgressMonitor) throws CoreException);

  pointcut cleanOutputFolders(boolean copyBack) :
    args(copyBack) &&
    execution(void BatchImageBuilder.cleanOutputFolders(boolean) throws CoreException);

  pointcut isJavaLikeFileName(String fileName) :
    args(fileName) &&
    execution(boolean Util.isJavaLikeFileName(String));

  pointcut filterExtraResource(IResource resource) :
    args(resource) &&
    execution(boolean JavaBuilder.filterExtraResource(IResource));

  void around(BatchImageBuilder builder, boolean copyBack) throws CoreException :
    target(builder) &&
    cleanOutputFolders(copyBack) &&
    cflow(build()) {
    // Suppress the cleaning behaviour but do the extra resource copying if requested
    if (copyBack)
      for (int i = 0, l = builder.sourceLocations.length; i < l; i++) {
        org.eclipse.jdt.internal.core.builder.ClasspathMultiDirectory sourceLocation = builder.sourceLocations[i];
        if (sourceLocation.hasIndependentOutputFolder)
          builder.copyExtraResourcesBack(sourceLocation, false);
      }
  }

  boolean around(String fileName) :
    isJavaLikeFileName(fileName) &&
    cflow(build()) &&
    !cflow(cleanOutputFolders(*)) {
    if (fileName != null && fileName.endsWith("scala"))
      return false;
    else
      return proceed(fileName);
  }

  boolean around(IResource resource) :
    filterExtraResource(resource) &&
    cflow(build()) {
    return resource.getName().endsWith(".scala") || proceed(resource);
  }
}


* update aspects

* update aspects

package ru.agentlab.maia.context.aj;

import ru.agentlab.maia.context.IMaiaContext;
import ru.agentlab.maia.context.IMaiaContextFactory;

/**
 * Abstract aspect with pointcuts of {@link IMaiaContextFactory} methods
 *
 * @author Shishkin Dmitriy
 *
 */
public abstract aspect AMaiaContextFactory {

	/**
	 * When actual IMaiaContextFactory object is created
	 */
	public pointcut onConstruct() :
		execution(IMaiaContextFactory+.new(..));

	/**
	 * When {@link IMaiaContextFactory} begin to create new {@link IMaiaContext}
	 *
	 * @param factory
	 *            - factory that create new context
	 * @param name
	 *            - name for new context
	 */
	public pointcut onCreateContext(IMaiaContextFactory factory) :
		execution(IMaiaContext IMaiaContextFactory+.createContext()) &&
		target(factory) &&
		args();

}

package ru.agentlab.maia.context.aj;

import ru.agentlab.maia.context.IMaiaContext;
import ru.agentlab.maia.context.IMaiaContextFactory;

/**
 * Abstract aspect with pointcuts of {@link IMaiaContextFactory} methods
 *
 * @author Shishkin Dmitriy
 *
 */
public abstract aspect AMaiaContextFactory {

	/**
	 * When actual IMaiaContextFactory object is created
	 */
	public pointcut onConstruct() :
		execution(IMaiaContextFactory+.new(..));

	/**
	 * When {@link IMaiaContextFactory} begin to create new child
	 * {@link IMaiaContext}
	 *
	 * @param factory
	 *            - factory that create new context
	 * @param parent
	 *            - parent context for newly created context
	 * @param name
	 *            - name for new context
	 */
	public pointcut onCreateChildContext(IMaiaContextFactory factory,
			IMaiaContext parent, String name) :
		execution(IMaiaContext IMaiaContextFactory+.createChild(IMaiaContext, String)) &&
		target(factory) &&
		args(parent, name);

	/**
	 * When {@link IMaiaContextFactory} begin to create new {@link IMaiaContext}
	 *
	 * @param factory
	 *            - factory that create new context
	 * @param name
	 *            - name for new context
	 */
	public pointcut onCreateContext(IMaiaContextFactory factory, String name) :
		execution(IMaiaContext IMaiaContextFactory+.createContext(String)) &&
		target(factory) &&
		args(name);

}


update pointcut name

update pointcut name

git-svn-id: 611fcff3babe92294e93aa7369573f5fe3352ff6@1089 468f3c8c-1912-47de-8909-53fc7abe8733

package org.entitypedia.games.common.mixins;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.entitypedia.games.common.util.HibernateAwareObjectMapper;
import org.entitypedia.games.common.util.MapperSavingJackson2HttpMessageConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.access.intercept.aspectj.MethodInvocationAdapter;
import org.springframework.security.access.prepost.PreInvocationAttribute;
import org.springframework.security.access.prepost.PreInvocationAuthorizationAdvice;
import org.springframework.security.access.prepost.PrePostInvocationAttributeFactory;
import org.springframework.security.core.context.SecurityContextHolder;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Applies mixins from annotations to Jackson Mapper.
 *
 * @author Aliaksandr Autayeu
 */
public aspect JacksonMixinSetupAspect implements InitializingBean {

    private static final Logger log = LoggerFactory.getLogger(JacksonMixinSetupAspect.class);

    private PreInvocationAuthorizationAdvice preAdvice;

    private PrePostInvocationAttributeFactory attributeFactory;

    private MapperSavingJackson2HttpMessageConverter converter;

    private Constructor<MethodInvocationAdapter> constructor;

    private final static ThreadLocal<ObjectMapper> tlObjectMapper = new ThreadLocal<ObjectMapper>() {
        @Override protected ObjectMapper initialValue() {
            return null;
        }
    };
    private final static ThreadLocal<List<JacksonMixin>> tlMixins = new ThreadLocal<List<JacksonMixin>>() {
        @Override
        protected List<JacksonMixin> initialValue() {
            return new ArrayList<JacksonMixin>(4);
        }
    };

    @Override
    public void afterPropertiesSet() throws Exception {
        // hack package local constructor...
        constructor = MethodInvocationAdapter.class.getDeclaredConstructor(JoinPoint.class);
        constructor.setAccessible(true);

        if (null == preAdvice) {
            throw new IllegalArgumentException("org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice required!");
        }
        if (null == attributeFactory) {
            throw new IllegalArgumentException("org.springframework.security.access.expression.method.ExpressionBasedAnnotationAttributeFactory required!");
        }
        if (null == converter) {
            throw new IllegalArgumentException("org.entitypedia.games.common.util.MapperSavingJackson2HttpMessageConverter required!");
        }
    }

    pointcut controllerExecution(): execution(@JacksonMixins * org.entitypedia.games..api.controller..*(..));
    after() returning: controllerExecution() {
        // this prepares mixins for the filtering mapper.

        // one needs to check the control flow of the annotated method to ensure
        // there is no other mixins-annotated method being called inside the body
        // e.g. createGame calls readCrossword, both annotated
        // and the latter overwrites the mixins of the former
        // in this case warning is printed

        // jackson allows only one mixin per class... can't accumulate mixins over complex call trees

        // mixins clear in writeInternal. therefore they might get stuck if method does not render JSON
        // e.g. updateFocus calls readGame for access control and mixins get stuck.
        // in this case warning is printed too, but it has little meaning

        if (thisJoinPoint.getSignature() instanceof MethodSignature) {
            MethodSignature signature = (MethodSignature) thisJoinPoint.getSignature();
            // gets interface method
            Method method = signature.getMethod();
            if (null != method) {
                JacksonMixins annotation = method.getAnnotation(JacksonMixins.class);
                if (null != annotation && null != annotation.value() && 0 < annotation.value().length) {
                    List<JacksonMixin> mixins = tlMixins.get();
                    if (0 < mixins.size()) {
                        if (log.isWarnEnabled()) {
                            log.warn("{} is overwriting mixins {}", thisJoinPoint, Arrays.toString(mixinsToStringArray(mixins)));
                        }
                    }
                    mixins.clear();

                    for (int i = 0; i < annotation.value().length; i++) {
                        JacksonMixin mixin = annotation.value()[i];
                        boolean apply = true;
                        if (!"".equals(mixin.condition())) {
                            // evaluate condition
                            PreInvocationAttribute attribute = attributeFactory.createPreInvocationAttribute(null, null, mixin.condition());
                            try {
                                apply = preAdvice.before(
                                        SecurityContextHolder.getContext().getAuthentication(),
                                        constructor.newInstance(thisJoinPoint),
                                        attribute);
                            } catch (InstantiationException e) {
                                throw new RuntimeException(e);
                            } catch (IllegalAccessException e) {
                                throw new RuntimeException(e);
                            } catch (InvocationTargetException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        if (apply) {
                            mixins.add(mixin);
                        }
                    }

                    if (0 < mixins.size()) {
                        if (log.isDebugEnabled()) {
                            log.debug("{} prepared mixins {}", thisJoinPoint, Arrays.toString(mixinsToStringArray(mixins)));
                        }
                    } else {
                        log.debug("{} cleared mixins", thisJoinPoint);
                    }
                }
            }
        }
    }

    private String[] mixinsToStringArray(List<JacksonMixin> mixins) {
        String[] result = new String[mixins.size()];
        for (int i = 0; i < mixins.size(); i++) {
            result[i] = mixins.get(i).target().getSimpleName() + "=" + mixins.get(i).mixin().getSimpleName();
        }
        return result;
    }

    pointcut writeInternalExecution(): execution(* org.entitypedia.games.common.util.MapperSavingJackson2HttpMessageConverter.writeInternal(..));
    Object around(): writeInternalExecution() {
        List<JacksonMixin> mixins = tlMixins.get();
        if (0 < mixins.size()) {
            // set up mapper
            ObjectMapper mapper = new HibernateAwareObjectMapper();
            for (JacksonMixin mixin : mixins) {
                mapper.addMixInAnnotations(mixin.target(), mixin.mixin());
            }

            log.debug("Applying mixins {} to {}", Arrays.toString(mixinsToStringArray(mixins)), converter);
            tlObjectMapper.set(converter.getObjectMapper());
            converter.setObjectMapper(mapper);
        }

        Object result = proceed();

        if (0 < mixins.size()) {
            // restore mapper
            log.debug("Removing mixins from {}", converter);
            converter.setObjectMapper(tlObjectMapper.get());

            // clean up vars
            tlObjectMapper.set(null);
            mixins.clear();
        }

        return result;
    }

    public void setConverter(MapperSavingJackson2HttpMessageConverter converter) {
        this.converter = converter;
    }

    public void setAttributeFactory(PrePostInvocationAttributeFactory attributeFactory) {
        this.attributeFactory = attributeFactory;
    }

    public void setPreAdvice(PreInvocationAuthorizationAdvice preAdvice) {
        this.preAdvice = preAdvice;
    }
}

package org.entitypedia.games.common.mixins;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.entitypedia.games.common.util.HibernateAwareObjectMapper;
import org.entitypedia.games.common.util.MapperSavingJackson2HttpMessageConverter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.access.intercept.aspectj.MethodInvocationAdapter;
import org.springframework.security.access.prepost.PreInvocationAttribute;
import org.springframework.security.access.prepost.PreInvocationAuthorizationAdvice;
import org.springframework.security.access.prepost.PrePostInvocationAttributeFactory;
import org.springframework.security.core.context.SecurityContextHolder;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Applies mixins from annotations to Jackson Mapper.
 *
 * @author Aliaksandr Autayeu
 */
public aspect JacksonMixinSetupAspect implements InitializingBean {

    private static final Logger log = LoggerFactory.getLogger(JacksonMixinSetupAspect.class);

    private PreInvocationAuthorizationAdvice preAdvice;

    private PrePostInvocationAttributeFactory attributeFactory;

    private MapperSavingJackson2HttpMessageConverter converter;

    private Constructor<MethodInvocationAdapter> constructor;

    private final static ThreadLocal<ObjectMapper> tlObjectMapper = new ThreadLocal<ObjectMapper>() {
        @Override protected ObjectMapper initialValue() {
            return null;
        }
    };
    private final static ThreadLocal<List<JacksonMixin>> tlMixins = new ThreadLocal<List<JacksonMixin>>() {
        @Override
        protected List<JacksonMixin> initialValue() {
            return new ArrayList<JacksonMixin>(4);
        }
    };

    @Override
    public void afterPropertiesSet() throws Exception {
        // hack package local constructor...
        constructor = MethodInvocationAdapter.class.getDeclaredConstructor(JoinPoint.class);
        constructor.setAccessible(true);

        if (null == preAdvice) {
            throw new IllegalArgumentException("org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice required!");
        }
        if (null == attributeFactory) {
            throw new IllegalArgumentException("org.springframework.security.access.expression.method.ExpressionBasedAnnotationAttributeFactory required!");
        }
        if (null == converter) {
            throw new IllegalArgumentException("org.entitypedia.games.common.util.MapperSavingJackson2HttpMessageConverter required!");
        }
    }

    pointcut serviceExecution(): execution(@JacksonMixins * org.entitypedia.games..api.controller..*(..));
    after() returning: serviceExecution() {
        // this prepares mixins for the filtering mapper.

        // one needs to check the control flow of the annotated method to ensure
        // there is no other mixins-annotated method being called inside the body
        // e.g. createGame calls readCrossword, both annotated
        // and the latter overwrites the mixins of the former
        // in this case warning is printed

        // jackson allows only one mixin per class... can't accumulate mixins over complex call trees

        // mixins clear in writeInternal. therefore they might get stuck if method does not render JSON
        // e.g. updateFocus calls readGame for access control and mixins get stuck.
        // in this case warning is printed too, but it has little meaning

        if (thisJoinPoint.getSignature() instanceof MethodSignature) {
            MethodSignature signature = (MethodSignature) thisJoinPoint.getSignature();
            // gets interface method
            Method method = signature.getMethod();
            if (null != method) {
                JacksonMixins annotation = method.getAnnotation(JacksonMixins.class);
                if (null != annotation && null != annotation.value() && 0 < annotation.value().length) {
                    List<JacksonMixin> mixins = tlMixins.get();
                    if (0 < mixins.size()) {
                        if (log.isWarnEnabled()) {
                            log.warn("{} is overwriting mixins {}", thisJoinPoint, Arrays.toString(mixinsToStringArray(mixins)));
                        }
                    }
                    mixins.clear();

                    for (int i = 0; i < annotation.value().length; i++) {
                        JacksonMixin mixin = annotation.value()[i];
                        boolean apply = true;
                        if (!"".equals(mixin.condition())) {
                            // evaluate condition
                            PreInvocationAttribute attribute = attributeFactory.createPreInvocationAttribute(null, null, mixin.condition());
                            try {
                                apply = preAdvice.before(
                                        SecurityContextHolder.getContext().getAuthentication(),
                                        constructor.newInstance(thisJoinPoint),
                                        attribute);
                            } catch (InstantiationException e) {
                                throw new RuntimeException(e);
                            } catch (IllegalAccessException e) {
                                throw new RuntimeException(e);
                            } catch (InvocationTargetException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        if (apply) {
                            mixins.add(mixin);
                        }
                    }

                    if (0 < mixins.size()) {
                        if (log.isDebugEnabled()) {
                            log.debug("{} prepared mixins {}", thisJoinPoint, Arrays.toString(mixinsToStringArray(mixins)));
                        }
                    } else {
                        log.debug("{} cleared mixins", thisJoinPoint);
                    }
                }
            }
        }
    }

    private String[] mixinsToStringArray(List<JacksonMixin> mixins) {
        String[] result = new String[mixins.size()];
        for (int i = 0; i < mixins.size(); i++) {
            result[i] = mixins.get(i).target().getSimpleName() + "=" + mixins.get(i).mixin().getSimpleName();
        }
        return result;
    }

    pointcut writeInternalExecution(): execution(* org.entitypedia.games.common.util.MapperSavingJackson2HttpMessageConverter.writeInternal(..));
    Object around(): writeInternalExecution() {
        List<JacksonMixin> mixins = tlMixins.get();
        if (0 < mixins.size()) {
            // set up mapper
            ObjectMapper mapper = new HibernateAwareObjectMapper();
            for (JacksonMixin mixin : mixins) {
                mapper.addMixInAnnotations(mixin.target(), mixin.mixin());
            }

            log.debug("Applying mixins {} to {}", Arrays.toString(mixinsToStringArray(mixins)), converter);
            tlObjectMapper.set(converter.getObjectMapper());
            converter.setObjectMapper(mapper);
        }

        Object result = proceed();

        if (0 < mixins.size()) {
            // restore mapper
            log.debug("Removing mixins from {}", converter);
            converter.setObjectMapper(tlObjectMapper.get());

            // clean up vars
            tlObjectMapper.set(null);
            mixins.clear();
        }

        return result;
    }

    public void setConverter(MapperSavingJackson2HttpMessageConverter converter) {
        this.converter = converter;
    }

    public void setAttributeFactory(PrePostInvocationAttributeFactory attributeFactory) {
        this.attributeFactory = attributeFactory;
    }

    public void setPreAdvice(PreInvocationAuthorizationAdvice preAdvice) {
        this.preAdvice = preAdvice;
    }
}