content: package net.anotheria.moskito.aop.aspect;
  
  import org.aspectj.lang.ProceedingJoinPoint;
  import org.aspectj.lang.Signature;
  import org.aspectj.lang.annotation.Around;
  import org.aspectj.lang.annotation.Aspect;
  import net.anotheria.moskito.aop.annotation.Monitor;
  import org.aspectj.lang.reflect.MethodSignature;
  
  import java.lang.reflect.Method;
  
  import static net.anotheria.moskito.core.util.annotation.AnnotationUtils.findAnnotation;
  import static net.anotheria.moskito.core.util.annotation.AnnotationUtils.findTypeAnnotation;
  
  /**
   * Aspect used to intercept @Monitor annotated classes method calls.
   *
   * @author Vitaliy Zhovtiuk
   * @author lrosenberg
   * @author bvanchuhov
   */
  @Aspect
  public class MonitoringAspect extends MonitoringBaseAspect{
  
  	/**
  	 * Common method profiling entry-point.
  	 *
  	 * @param pjp
  	 * 		{@link ProceedingJoinPoint}
  	 * @param method
  	 * 		{@link Monitor}
  	 * @return call result
  	 * @throws Throwable
  	 * 		in case of error during {@link ProceedingJoinPoint#proceed()}
  	 */
  	@Around(value = "execution(* *(..)) && (@annotation(method))")
      public Object doProfilingMethod(ProceedingJoinPoint pjp, Monitor method) throws Throwable {
      	return doProfiling(pjp, method.producerId(), method.subsystem(), method.category());
      }
  
  	/**
  	 * Common class profiling entry-point.
  	 *
  	 * @param pjp
  	 * 		{@link ProceedingJoinPoint}
  	 * @param monitor
  	 * 		{@link Monitor}
  	 * @return call result
  	 * @throws Throwable
  	 * 		in case of error during {@link ProceedingJoinPoint#proceed()}
  	 */
      @Around(value = "execution(* *.*(..)) && @within(monitor) && !@annotation(net.anotheria.moskito.aop.annotation.DontMonitor)")
      public Object doProfilingClass(ProceedingJoinPoint pjp, Monitor monitor) throws Throwable {
          return doProfiling(pjp, monitor.producerId(), monitor.subsystem(), monitor.category());
      }
  
  	/**
  	 * Special method profiling entry-point, which allow to fetch {@link Monitor} from one lvl up of method annotation.
  	 * Pattern will pre-select 2-nd lvl annotation on method scope.
  	 *
  	 * @param pjp
  	 * 		{@link ProceedingJoinPoint}
  	 * @return call result
  	 * @throws Throwable
  	 * 		in case of error during {@link ProceedingJoinPoint#proceed()}
  	 */
  	@Around (value = "execution(@(@net.anotheria.moskito.aop.annotation.Monitor *) * *(..)) && !@annotation(net.anotheria.moskito.aop.annotation.DontMonitor)")
  	public Object doProfilingMethod(final ProceedingJoinPoint pjp) throws Throwable {
  		return doProfilingMethod(pjp, resolveAnnotation(pjp));
  	}
  
  	/**
  	 * Special class profiling entry-point, which allow to fetch {@link Monitor} from one lvl up of class annotation.
  	 * Pattern will pre-select 2-nd lvl annotation on class scope.
  	 *
  	 * @param pjp
  	 * 		{@link ProceedingJoinPoint}
  	 * @return call result
  	 * @throws Throwable
  	 * 		in case of error during {@link ProceedingJoinPoint#proceed()}
  	 */
  	@Around (value = "execution(* (@(@net.anotheria.moskito.aop.annotation.Monitor *) *).*(..)) && !@annotation(net.anotheria.moskito.aop.annotation.DontMonitor)")
  	public Object doProfilingClass(final ProceedingJoinPoint pjp) throws Throwable {
  		return doProfilingClass(pjp, resolveAnnotation(pjp));
  	}
  
  	/**
  	 * Trying to resolve {@link Monitor} annotation first in method annotations scope, then in class scope. Note - method will also check
  	 * if {@link Monitor} is Placed to some other annotation as meta!
  	 * Search order :
  	 * - 1 method;
  	 * - 2 type.
  	 *
  	 * @param pjp
  	 * 		{@link ProceedingJoinPoint}
  	 * @return {@link Monitor} or {@code null}
  	 */
  	private Monitor resolveAnnotation(final ProceedingJoinPoint pjp) {
  		final Signature signature = pjp.getSignature();
  		final Class<?> type = signature.getDeclaringType();
  		final Method method = (signature instanceof MethodSignature) ? MethodSignature.class.cast(signature).getMethod() :
  				null;
  		final Monitor methodMonitorAnn = method != null ? findAnnotation(method, Monitor.class) : null;
  		if (methodMonitorAnn != null)
  			return methodMonitorAnn;
  		return findTypeAnnotation(type, Monitor.class);
  	}
  }
  