/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2006-2009 Sun Microsystems, Inc.
 */
package org.opends.server.loggers.debug;

import org.aspectj.lang.annotation.*;
import org.aspectj.lang.JoinPoint;

import static
    org.opends.server.loggers.debug.DebugLogger.DEFAULT_CONSTRUCTOR_LEVEL;
import static
    org.opends.server.loggers.debug.DebugLogger.DEFAULT_ENTRY_EXIT_LEVEL;
import static
    org.opends.server.loggers.debug.DebugLogger.DEFAULT_THROWN_LEVEL;

/**
 * An aspect for source-code tracing at the method level.
 *
 * One DebugLogger aspect instance exists for each Java class using tracing.
 * Tracer must be registered with the DebugLogger.
 *
 * Logging is always done at a level basis, with debug log messages
 * exceeding the trace threshold being traced, others being discarded.
 */
@Aspect()
public class DebugAspect
{
  /**
   * Pointcut for matching static context events.
   */
  @Pointcut("!this(Object)")
  private void staticContext()
  {
  }

  /**
   * Pointcut for matching non static context events.
   * @param obj The object being operated on.
   */
  @Pointcut("this(obj)")
  private void nonStaticContext(Object obj)
  {
  }

  /**
   * Pointcut for matching all toString() methods.
   */
  @Pointcut("execution(* *..toString(..))")
  private void toStringMethod()
  {
  }

  /**
   * Pointcut for matching all getMessage() methods.
   */
  @Pointcut("execution(String org.opends.server." +
      "messages.MessageHandler.getMessage(..))")
  private void getMessageMethod()
  {
  }

  /**
   * Pointcut for matching all getDebugProperties() methods.
   * TODO: Make this less general. Find out if pointcut matches
   * subclass methods.
   */
  @Pointcut("execution(* *..getDebugProperties(..))")
  private void getDebugPropertiesMethod()
  {
  }

  /**
   * Pointcut for matching all logging related classes.
   */
  @Pointcut("within(org.opends.server.loggers.*+ || " +
    "org.opends.server.loggers.debug..*+ || " +
    "org.opends.server.types.DebugLogLevel+ || " +
    "org.opends.server.types.DebugLogCategory+ || " +
    "org.opends.server.api.DebugLogPublisher+ || " +
    "org.opends.server.util.TimeThread+ ||" +
    "org.opends.server.util.MultiOutputStream+)")
  private void debugRelatedClasses()
  {
  }

  /**
   * Pointcut to exclude all pointcuts which should not be adviced by the
   * debug logger.
   */
  @Pointcut("toStringMethod() || getMessageMethod() || " +
      "getDebugPropertiesMethod() || debugRelatedClasses()")
  private void excluded()
  {
  }

  /**
   * Pointcut for matching the execution of all public methods.
   */
  @Pointcut("execution(!@(DebugLogger.NoDebugTracing || " +
      "DebugLogger.NoEntryDebugTracing) public * *(..)) && " +
      "!excluded()")
  void tracedEntryMethod()
  {
  }

  /**
   * Pointcut for matching the execution of all public methods.
   */
  @Pointcut("execution(!@(DebugLogger.NoDebugTracing || " +
      "DebugLogger.NoExitDebugTracing) public * *(..)) && " +
      "!excluded()")
  void tracedExitMethod()
  {
  }

  /**
   * Pointcut for matching the execution of all public methods.
   */
  @Pointcut("execution(@DebugLogger.TraceThrown public * *(..)) && " +
      "!excluded()")
  void tracedThrownMethod()
  {
  }

  /**
   * Pointcut for matching the execution of all constructors.
   */
  @Pointcut("execution(!@(DebugLogger.NoDebugTracing || " +
      "DebugLogger.NoEntryDebugTracing) public new(..)) && !excluded()")
  void tracedEntryConstructor()
  {
  }

  /**
   * Pointcut for matching the execution of all constructors.
   */
  @Pointcut("execution(!@(DebugLogger.NoDebugTracing || " +
      "DebugLogger.NoExitDebugTracing) public new(..)) && !excluded()")
  void tracedExitConstructor()
  {
  }

  /**
   * Pointcut for matching only if there are publishers.
   *
   * @return if debug logging is enabled.
   */
  @Pointcut("if()")
  public static boolean shouldTrace()
  {
    return DebugLogger.enabled;
  }

  /**
   * AspectJ Implementation.
   *
   * @param thisJoinPoint the JoinPoint reflection object.
   */
  @Before("shouldTrace() && tracedEntryConstructor()")
  public void traceConstructor(JoinPoint thisJoinPoint)
  {
    String signature =
        thisJoinPoint.getSignature().getDeclaringTypeName();
    Object[] args = thisJoinPoint.getArgs();
    DebugTracer tracer = DebugLogger.getTracer(signature);
    if(tracer != null)
    {
      tracer.debugConstructor(
          DEFAULT_CONSTRUCTOR_LEVEL, args);
    }
  }

  /**
   * AspectJ Implementation.
   *
   * @param thisJoinPoint the JoinPoint reflection object.
   * @param obj the object this method operations on.
   */
  @Before("shouldTrace() && tracedEntryMethod() && nonStaticContext(obj)")
  public void traceNonStaticMethodEntry(JoinPoint thisJoinPoint, Object obj)
  {
    String signature =
        thisJoinPoint.getSignature().getDeclaringTypeName();
    Object[] args = thisJoinPoint.getArgs();
    Object callerInstance = thisJoinPoint.getThis();
    DebugTracer tracer = DebugLogger.getTracer(signature);
    if(tracer != null)
    {
      tracer.debugMethodEntry(
          DEFAULT_ENTRY_EXIT_LEVEL, callerInstance, args);
    }
  }

  /**
   * AspectJ Implementation.
   *
   * @param thisJoinPoint the JoinPoint reflection object.
   */
  @Before("shouldTrace() && tracedEntryMethod() && staticContext()")
  public void traceStaticMethodEntry(JoinPoint thisJoinPoint)
  {
    String signature =
        thisJoinPoint.getSignature().getDeclaringTypeName();
    Object[] args = thisJoinPoint.getArgs();
    DebugTracer tracer = DebugLogger.getTracer(signature);
    if(tracer != null)
    {
      tracer.debugStaticMethodEntry(
          DEFAULT_ENTRY_EXIT_LEVEL, args);
    }
  }

  /**
   * AspectJ Implementation.
   *
   * @param thisJoinPointStaticPart the JoinPoint reflection object.
   * @param ret the return value of the method.
   */
  @AfterReturning(pointcut = "shouldTrace() && " +
      "(tracedExitMethod() || tracedExitConstructor())",
                  returning = "ret")
  public void traceReturn(JoinPoint.StaticPart thisJoinPointStaticPart,
                          Object ret)
  {
    String signature =
        thisJoinPointStaticPart.getSignature().getDeclaringTypeName();
    DebugTracer tracer = DebugLogger.getTracer(signature);
    if(tracer != null)
    {
      tracer.debugReturn(
          DEFAULT_ENTRY_EXIT_LEVEL, ret);
    }
  }

  /**
   * AspectJ Implementation.
   *
   * @param thisJoinPointStaticPart the JoinPoint reflection object.
   * @param ex the exception thrown.
   */
  @SuppressAjWarnings({"adviceDidNotMatch"})
  @AfterThrowing(pointcut = "shouldTrace() && tracedThrownMethod()",
                 throwing = "ex")
  public void traceThrown(JoinPoint.StaticPart thisJoinPointStaticPart,
                          Throwable ex)
  {
    String signature =
        thisJoinPointStaticPart.getSignature().getDeclaringTypeName();
    DebugTracer tracer = DebugLogger.getTracer(signature);
    if(tracer != null)
    {
      tracer.debugThrown(
          DEFAULT_THROWN_LEVEL, ex);
    }
  }
}
