All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.avaje.metric.agent.ClassAdapterMetric Maven / Gradle / Ivy

There is a newer version: 4.3.2
Show newest version
package org.avaje.metric.agent;

import java.util.ArrayList;
import java.util.List;

import org.avaje.metric.agent.asm.AnnotationVisitor;
import org.avaje.metric.agent.asm.ClassVisitor;
import org.avaje.metric.agent.asm.Label;
import org.avaje.metric.agent.asm.MethodVisitor;
import org.avaje.metric.agent.asm.Opcodes;

/**
 * ClassAdapter used to add metrics collection.
 */
public class ClassAdapterMetric extends ClassVisitor implements Opcodes {

  private static final String SINGLETON = "/Singleton;";

  private static final String SPRINGFRAMEWORK_STEREOTYPE = "Lorg/springframework/stereotype";

  private static final String ANNOTATION_TIMED = "Lorg/avaje/metric/annotation/Timed;";

  private static final String ANNOTATION_NOT_TIMED = "Lorg/avaje/metric/annotation/NotTimed;";

  private static final String ANNOTATION_ALREADY_ENHANCED_MARKER = "Lorg/avaje/metric/spi/AlreadyEnhancedMarker;";

  protected final EnhanceContext enhanceContext;

  protected final ClassLoader classLoader;

  private boolean markerAnnotationAdded;

  private boolean detectSingleton;
  
  private boolean detectJaxrs;

  private boolean detectSpringComponent;

  private boolean detectExplicit;

  private boolean shouldBeEnhanced;

  private boolean existingStaticInitialiser;

  protected String className;

  /**
   * List of unique names to support parameter overloading.
   */
  private final ArrayList uniqueMethodNames = new ArrayList();

  /**
   * The method adapters that detect if a method is enhanced and perform the enhancement.
   */
  private final List methodAdapters = new ArrayList();

  /**
   * The metric full name that is a common prefix for each method.
   */
  private String metricFullName;
    
  /**
   * The buckets defined commonly for all enhanced methods for this class.
   */
  private int[] buckets;
  
  /**
   * Construct with visitor, context and classLoader.
   */
  public ClassAdapterMetric(ClassVisitor cv, EnhanceContext context, ClassLoader classLoader) {
    super(ASM4, cv);
    this.enhanceContext = context;
    this.classLoader = classLoader;
  }

  protected boolean isLog(int level) {
    return enhanceContext.isLog(level);
  }
  
  protected void log(int level, String msg) {
    if (isLog(level)) {
      enhanceContext.log(className, msg);
    }
  }

  protected void log(int level, String msg, String extra, String extra2, String extra3) {
    if (isLog(level)) {
      enhanceContext.log(className, msg + extra + extra2 + extra3);
    }
  }

  protected void log(int level, String msg, String extra) {
    if (isLog(level)) {
      enhanceContext.log(className, msg + extra);
    }
  }

  protected void log(String msg) {
    enhanceContext.log(className, msg);
  }

  /**
   * Set default buckets to use for methods enhanced for this class.
   */
  protected void setBuckets(Object value) {
    this.buckets = (int[])value;
  }
  
  /**
   * Return true if there are default buckets defined at the class level.
   */
  protected boolean hasBuckets() {
    return buckets != null && buckets.length > 0;
  }
  
  /**
   * Return the bucket ranges.
   */
  protected int[] getBuckets() {
    return buckets;
  }
  
  /**
   * Return the class level metric name which is used to prefix the metrics created for associated
   * methods on this class.
   */
  protected String getMetricFullName() {
    return metricFullName;
  }

  /**
   * Set the metric name via Timer annotation.
   */
  protected void setMetricName(String metricName) {
    int pos = metricFullName.lastIndexOf('.');
    if (pos == -1) {
      this.metricFullName = metricName;
    } else {
      this.metricFullName = metricFullName.substring(0, pos) + "." + metricName;
    }
  }
  
  /**
   * Set the full metric name for this class.
   */
  private void setMetricFullName(String className) {
    this.metricFullName = className.replace('/', '.');
  }

  @Override
  public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {

    super.visit(version, access, name, signature, superName, interfaces);
    if ((access & Opcodes.ACC_INTERFACE) != 0) {
      throw new NoEnhancementRequiredException("Not enhancing interface");
    }

    this.className = name;
    setMetricFullName(className);
  }

  /**
   * Visit class level annotations.
   */
  @Override
  public AnnotationVisitor visitAnnotation(String desc, boolean visible) {

    AnnotationVisitor av = super.visitAnnotation(desc, visible);

    log(8, "... check annotation ", desc);

    if (desc.equals(ANNOTATION_ALREADY_ENHANCED_MARKER)) {
      throw new AlreadyEnhancedException("Already enhanced");
    }

    if (desc.equals(ANNOTATION_NOT_TIMED)) {
      throw new NoEnhancementRequiredException("marked as NotTimed");
    }

    if (desc.equals(ANNOTATION_TIMED)) {
      log(5, "found Timed annotation ", desc);
      detectExplicit = true;
      shouldBeEnhanced = true;
      // read the name and bucket ranges from the class level Timer annotation
      return new TimedAnnotationVisitor(av);
    }
    
    if (enhanceContext.isEnhanceSingleton() && desc.endsWith(SINGLETON)) {
      detectSingleton = true;
      shouldBeEnhanced = true;      
    }

    if (isJaxRsEndpoint(desc)) {
      detectJaxrs = true;
      shouldBeEnhanced = true;
    }

    // We are interested in Service, Controller, Component etc
    if (desc.startsWith(SPRINGFRAMEWORK_STEREOTYPE)) {
      detectSpringComponent = true;
      shouldBeEnhanced = true;
    }
    return av;
  }

  private boolean isJaxRsEndpoint(String desc) {

    return desc.equals("Ljavax/ws/rs/Path;") 
        || desc.equals("Ljavax/ws/rs/Produces;")
        || desc.equals("Ljavax/ws/rs/Consumes;");
  }

  private void addMarkerAnnotation() {

    if (!markerAnnotationAdded) {
      if (isLog(3)) {
        String flagExplicit = (detectExplicit ? "EXPLICIT " : "");
        String flagJaxrs = (detectJaxrs ? "JAXRS " : "");
        String flagSpring = (detectSpringComponent ? "SPRING " : "");
        String flagSingleton = (detectSingleton ? "SINGLETON" : "");
        log(3, "enhancing - detection ", flagExplicit + flagJaxrs + flagSpring + flagSingleton );
      }
      AnnotationVisitor av = cv.visitAnnotation(ANNOTATION_ALREADY_ENHANCED_MARKER, true);
      if (av != null) {
        av.visitEnd();
      }
      markerAnnotationAdded = true;
    }
  }

  /**
   * Helper to read and set the name and fullName attributes of the Timed annotation.
   */
  private class TimedAnnotationVisitor extends AnnotationVisitor {

    public TimedAnnotationVisitor(AnnotationVisitor av) {
      super(ASM4, av);
    }

    @Override
    public void visit(String name, Object value) {
      if ("name".equals(name) && !"".equals(value)) {
        setMetricName(value.toString());
        
      } else if ("fullName".equals(name) && !"".equals(value)) {
        setMetricFullName(value.toString());
        
      } else if ("buckets".equals(name)) {
        setBuckets(value);
      }
    }
  }
  
  /**
   * Visit the methods specifically looking for method level transactional annotations.
   */
  @Override
  public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {

    if (!shouldBeEnhanced) {
      log(8, "... no marker annotations found ");
      throw new NoEnhancementRequiredException();
    } else {
      addMarkerAnnotation();
    }

    MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
    if (name.equals("")) {
      // not enhancing constructor
      log(5, "... not enhancing constructor:", name, " desc:", desc);
      return mv;
    }
    if (isCommonMethod(name, desc)) {
      // not enhancing constructor
      log(5, "... not enhancing:",  name, " desc:", desc);
      return mv;
    }
    if (name.equals("")) {
      // static initializer, add call _$initMetrics()
      log(2, "...  exists - adding call to _$initMetrics()", "");
      existingStaticInitialiser = true;
      return new StaticInitAdapter(mv, access, name, desc, className);
    }
    
    boolean publicMethod = isPublicMethod(access);
    int metricIndex = methodAdapters.size();
    String uniqueMethodName = deriveUniqueMethodName(name);
    if (isLog(8)) {
      log("... method:" + name + " public:" + publicMethod + " index:" + metricIndex + " uniqueMethodName:" + uniqueMethodName);
    }

    boolean enhanceByDefault = publicMethod;
    if ((access & Opcodes.ACC_STATIC) != 0) {
      // by default not enhancing static method unless it is explicitly
      // annotated with a Timed annotation
      enhanceByDefault = false;
      if (isLog(5)) {
        log(5, "... static method:",  name,  " desc:",  desc);
      }
    }
    
    // Not sure if we are enhancing this method yet ...
    AddTimerMetricMethodAdapter methodAdapter = createAdapter(enhanceByDefault, metricIndex, uniqueMethodName, mv, access, name, desc);
    methodAdapters.add(methodAdapter);
    return methodAdapter;
  }

  /**
   * Return true if a equals, hashCode or toString method - these are not enhanced.
   */
  private boolean isCommonMethod(String name, String desc) {
    
    if (name.equals("equals")) {
      return true;
    }
    if (name.equals("hashCode")) {
      return true;
    }
    if (name.equals("toString")) {
      return true;
    }
    return false;
  }

  private AddTimerMetricMethodAdapter createAdapter(boolean enhanceDefault, int metricIndex, String uniqueMethodName, MethodVisitor mv, int access, String name, String desc) {

    return new AddTimerMetricMethodAdapter(this, enhanceDefault, metricIndex, uniqueMethodName, mv, access, name, desc);
  }

  private boolean isPublicMethod(int access) {
    return ((access & Opcodes.ACC_PUBLIC) != 0);
  }

  /**
   * Create and return a unique method name in case of parameter overloading.
   */
  private String deriveUniqueMethodName(String methodName) {
    
    int i = 1;
    String uniqueMethodName = methodName;
    while (uniqueMethodNames.contains(uniqueMethodName)) {
      uniqueMethodName = methodName + (i++);
    }
    uniqueMethodNames.add(uniqueMethodName);
    return uniqueMethodName;
  }


  @Override
  public void visitEnd() {

    addStaticFieldDefinitions();
    addStaticFieldInitialisers();

    if (!existingStaticInitialiser) {
      log(3, "... add  to call _$initMetrics()");
      addStaticInitialiser();
    }

    super.visitEnd();
  }

  private void addStaticFieldDefinitions() {
    for (int i = 0; i < methodAdapters.size(); i++) {
      AddTimerMetricMethodAdapter methodAdapter = methodAdapters.get(i);
      methodAdapter.addFieldDefinition(cv, i);
    }
  }
  

  /**
   * Add the static _$initMetrics() method.
   */
  private void addStaticFieldInitialisers() {

    MethodVisitor mv = cv.visitMethod(ACC_PRIVATE + ACC_STATIC, "_$initMetrics", "()V", null, null);
    mv.visitCode();

    log(3, "... adding static _$initMetrics() method");

    for (int i = 0; i < methodAdapters.size(); i++) {

      AddTimerMetricMethodAdapter methodAdapter = methodAdapters.get(i);
      methodAdapter.addFieldInitialisation(mv, i);
    }

    mv.visitInsn(RETURN);
    mv.visitMaxs(1, 0);
    mv.visitEnd();
  }

  /**
   * Add a static initialization block when there was not one on the class.
   */
  private void addStaticInitialiser() {
    
    MethodVisitor mv = cv.visitMethod(ACC_STATIC, "", "()V", null, null);
    mv.visitCode();
    Label l0 = new Label();
    mv.visitLabel(l0);
    mv.visitLineNumber(16, l0);
    mv.visitMethodInsn(INVOKESTATIC, className, "_$initMetrics", "()V");
    Label l1 = new Label();
    mv.visitLabel(l1);
    mv.visitLineNumber(17, l1);
    mv.visitInsn(RETURN);
    mv.visitMaxs(0, 0);
    mv.visitEnd();
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy