package org.skywalking.apm.agent.core.plugin.interceptor.enhance;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.implementation.bind.annotation.FieldProxy;
import net.bytebuddy.matcher.ElementMatchers;
import org.skywalking.apm.agent.core.plugin.PluginException;
import org.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
import org.skywalking.apm.agent.core.plugin.interceptor.EnhanceException;
import org.skywalking.apm.agent.core.plugin.interceptor.EnhancedClassInstanceContext;
import org.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
import org.skywalking.apm.agent.core.plugin.interceptor.StaticMethodsInterceptPoint;
import org.skywalking.apm.util.StringUtil;
import org.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine;
import org.skywalking.apm.logging.ILog;
import org.skywalking.apm.logging.LogManager;
import static net.bytebuddy.jar.asm.Opcodes.ACC_PRIVATE;
import static net.bytebuddy.matcher.ElementMatchers.isStatic;
import static net.bytebuddy.matcher.ElementMatchers.not;
/**
* This class controls all enhance operations, including enhance constructors, instance methods and static methods. All
* the enhances base on three types interceptor point: {@link ConstructorInterceptPoint}, {@link
* InstanceMethodsInterceptPoint} and {@link StaticMethodsInterceptPoint} If plugin is going to enhance constructors,
* instance methods, or both, {@link ClassEnhancePluginDefine} will add a field of {@link
* EnhancedClassInstanceContext} type.
*
* @author wusheng
*/
public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePluginDefine {
private static final ILog logger = LogManager.getLogger(ClassEnhancePluginDefine.class);
/**
* New field name.
*/
public static final String CONTEXT_ATTR_NAME = "_$EnhancedClassInstanceContext";
/**
* Begin to define how to enhance class.
* After invoke this method, only means definition is finished.
*
* @param enhanceOriginClassName target class name
* @param newClassBuilder byte-buddy's builder to manipulate class bytecode.
* @return new byte-buddy's builder for further manipulation.
*/
@Override
protected DynamicType.Builder<?> enhance(String enhanceOriginClassName,
DynamicType.Builder<?> newClassBuilder) throws PluginException {
newClassBuilder = this.enhanceClass(enhanceOriginClassName, newClassBuilder);
newClassBuilder = this.enhanceInstance(enhanceOriginClassName, newClassBuilder);
return newClassBuilder;
}
/**
* Enhance a class to intercept constructors and class instance methods.
*
* @param enhanceOriginClassName target class name
* @param newClassBuilder byte-buddy's builder to manipulate class bytecode.
* @return new byte-buddy's builder for further manipulation.
*/
private DynamicType.Builder<?> enhanceInstance(String enhanceOriginClassName,
DynamicType.Builder<?> newClassBuilder) throws PluginException {
ConstructorInterceptPoint[] constructorInterceptPoints = getConstructorsInterceptPoints();
InstanceMethodsInterceptPoint[] instanceMethodsInterceptPoints = getInstanceMethodsInterceptPoints();
boolean existedConstructorInterceptPoint = false;
if (constructorInterceptPoints != null && constructorInterceptPoints.length > 0) {
existedConstructorInterceptPoint = true;
}
boolean existedMethodsInterceptPoints = false;
if (instanceMethodsInterceptPoints != null && instanceMethodsInterceptPoints.length > 0) {
existedMethodsInterceptPoints = true;
}
/**
* nothing need to be enhanced in class instance, maybe need enhance static methods.
*/
if (!existedConstructorInterceptPoint && !existedMethodsInterceptPoints) {
return newClassBuilder;
}
/**
* alter class source code.<br/>
*
* new class need:<br/>
* 1.add field '_$EnhancedClassInstanceContext' of type
* EnhancedClassInstanceContext <br/>
*
*/
newClassBuilder = newClassBuilder.defineField(CONTEXT_ATTR_NAME, EnhancedClassInstanceContext.class, ACC_PRIVATE);
/**
* 2. enhance constructors
*/
newClassBuilder = newClassBuilder.constructor(ElementMatchers.<MethodDescription>any()).intercept(SuperMethodCall.INSTANCE
.andThen(MethodDelegation.to(new DefaultClassConstructorInterceptor()).appendParameterBinder(FieldProxy.Binder.install(FieldGetter.class, FieldSetter.class))));
if (existedConstructorInterceptPoint) {
for (ConstructorInterceptPoint constructorInterceptPoint : constructorInterceptPoints) {
newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher()).intercept(SuperMethodCall.INSTANCE.andThen(
MethodDelegation.to(new ClassConstructorInterceptor(constructorInterceptPoint.getConstructorInterceptor()))
.appendParameterBinder(FieldProxy.Binder.install(FieldGetter.class, FieldSetter.class))));
}
}
/**
* 3. enhance instance methods
*/
if (existedMethodsInterceptPoints) {
for (InstanceMethodsInterceptPoint instanceMethodsInterceptPoint : instanceMethodsInterceptPoints) {
String interceptor = instanceMethodsInterceptPoint.getMethodsInterceptor();
if (StringUtil.isEmpty(interceptor)) {
throw new EnhanceException("no InstanceMethodsAroundInterceptor define to enhance class " + enhanceOriginClassName);
}
ClassInstanceMethodsInterceptor classMethodInterceptor = new ClassInstanceMethodsInterceptor(interceptor);
newClassBuilder =
newClassBuilder.method(not(isStatic()).and(instanceMethodsInterceptPoint.getMethodsMatcher())).intercept(MethodDelegation.to(classMethodInterceptor));
}
}
return newClassBuilder;
}
/**
* Constructor methods intercept point. See {@link ConstructorInterceptPoint}
*
* @return collections of {@link ConstructorInterceptPoint}
*/
protected abstract ConstructorInterceptPoint[] getConstructorsInterceptPoints();
/**
* Instance methods intercept point. See {@link InstanceMethodsInterceptPoint}
*
* @return collections of {@link InstanceMethodsInterceptPoint}
*/
protected abstract InstanceMethodsInterceptPoint[] getInstanceMethodsInterceptPoints();
/**
* Enhance a class to intercept class static methods.
*
* @param enhanceOriginClassName target class name
* @param newClassBuilder byte-buddy's builder to manipulate class bytecode.
* @return new byte-buddy's builder for further manipulation.
*/
private DynamicType.Builder<?> enhanceClass(String enhanceOriginClassName,
DynamicType.Builder<?> newClassBuilder) throws PluginException {
StaticMethodsInterceptPoint[] staticMethodsInterceptPoints = getStaticMethodsInterceptPoints();
if (staticMethodsInterceptPoints == null || staticMethodsInterceptPoints.length == 0) {
return newClassBuilder;
}
for (StaticMethodsInterceptPoint staticMethodsInterceptPoint : staticMethodsInterceptPoints) {
String interceptor = staticMethodsInterceptPoint.getMethodsInterceptor();
if (StringUtil.isEmpty(interceptor)) {
throw new EnhanceException("no StaticMethodsAroundInterceptor define to enhance class " + enhanceOriginClassName);
}
ClassStaticMethodsInterceptor classMethodInterceptor = new ClassStaticMethodsInterceptor(interceptor);
newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher())).intercept(MethodDelegation.to(classMethodInterceptor));
}
return newClassBuilder;
}
/**
* Static methods intercept point. See {@link StaticMethodsInterceptPoint}
*
* @return collections of {@link StaticMethodsInterceptPoint}
*/
protected abstract StaticMethodsInterceptPoint[] getStaticMethodsInterceptPoints();
}