package com.navercorp.pinpoint.profiler.plugin.xml.transformer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import com.navercorp.pinpoint.bootstrap.config.ProfilerConfig;
import com.navercorp.pinpoint.bootstrap.context.TraceContext;
import com.navercorp.pinpoint.bootstrap.instrument.InstrumentContext;
import com.navercorp.pinpoint.bootstrap.instrument.MethodFilter;
import com.navercorp.pinpoint.bootstrap.interceptor.scope.ExecutionPolicy;
import com.navercorp.pinpoint.bootstrap.plugin.monitor.DataSourceMonitorRegistry;
import com.navercorp.pinpoint.profiler.metadata.ApiMetaDataService;
import com.navercorp.pinpoint.profiler.plugin.MatchableClassFileTransformer;
import com.navercorp.pinpoint.profiler.plugin.xml.FieldInjector;
import com.navercorp.pinpoint.profiler.plugin.xml.GetterInjector;
import com.navercorp.pinpoint.profiler.plugin.xml.OverrideMethodInjector;
import com.navercorp.pinpoint.profiler.plugin.xml.interceptor.AnnotatedInterceptorInjector;
import com.navercorp.pinpoint.profiler.plugin.xml.interceptor.TargetAnnotatedInterceptorInjector;
public class DefaultClassFileTransformerBuilder implements ClassFileTransformerBuilder, ConditionalClassFileTransformerBuilder, RecipeBuilder<ClassRecipe> {
private final ProfilerConfig profilerConfig;
private final TraceContext traceContext;
private final InstrumentContext pluginContext;
private final List<ClassRecipe> recipes = new ArrayList<ClassRecipe>();
private final List<RecipeBuilder<ClassRecipe>> recipeBuilders = new ArrayList<RecipeBuilder<ClassRecipe>>();
private final ClassCondition condition;
private final String targetClassName;
private final DataSourceMonitorRegistry dataSourceMonitorRegistry;
private final ApiMetaDataService apiMetaDataService;
public DefaultClassFileTransformerBuilder(ProfilerConfig profilerConfig, TraceContext traceContext, DataSourceMonitorRegistry dataSourceMonitorRegistry, ApiMetaDataService apiMetaDataService, InstrumentContext pluginContext, String targetClassName) {
this(profilerConfig, traceContext, dataSourceMonitorRegistry, apiMetaDataService, pluginContext, targetClassName, null);
}
private DefaultClassFileTransformerBuilder(ProfilerConfig profilerConfig, TraceContext traceContext, DataSourceMonitorRegistry dataSourceMonitorRegistry, ApiMetaDataService apiMetaDataService,
InstrumentContext pluginContext, String targetClassName, ClassCondition condition) {
if (profilerConfig == null) {
throw new NullPointerException("profilerConfig must not be null");
}
if (traceContext == null) {
throw new NullPointerException("traceContext must not be null");
}
if (dataSourceMonitorRegistry == null) {
throw new NullPointerException("dataSourceMonitorRegistry must not be null");
}
if (apiMetaDataService == null) {
throw new NullPointerException("apiMetaDataService must not be null");
}
if (pluginContext == null) {
throw new NullPointerException("pluginContext must not be null");
}
this.profilerConfig = profilerConfig;
this.traceContext = traceContext;
this.dataSourceMonitorRegistry = dataSourceMonitorRegistry;
this.apiMetaDataService = apiMetaDataService;
this.pluginContext = pluginContext;
this.targetClassName = targetClassName;
this.condition = condition;
}
@Override
public void conditional(ClassCondition condition, ConditionalClassFileTransformerSetup describer) {
DefaultClassFileTransformerBuilder conditional = new DefaultClassFileTransformerBuilder(profilerConfig, traceContext, dataSourceMonitorRegistry, apiMetaDataService, pluginContext, targetClassName, condition);
describer.setup(conditional);
recipeBuilders.add(conditional);
}
@Override
public void injectGetter(String getterTyepName, String fieldName) {
recipes.add(new GetterInjector(getterTyepName, fieldName));
}
@Override
public void injectField(String accessorTypeName) {
recipes.add(new FieldInjector(accessorTypeName));
}
@Override
public void overrideMethodToDelegate(String name, String... paramTypes) {
recipes.add(new OverrideMethodInjector(name, paramTypes));
}
@Override
public InterceptorBuilder injectInterceptor(String className, Object... constructorArgs) {
TargetAnnotatedInterceptorInjectorBuilder builder = new TargetAnnotatedInterceptorInjectorBuilder(className, constructorArgs);
recipeBuilders.add(builder);
return builder;
}
@Override
public MethodTransformerBuilder editMethods(MethodFilter... filters) {
DefaultMethodEditorBuilder builder = new DefaultMethodEditorBuilder(filters);
recipeBuilders.add(builder);
return builder;
}
@Override
public MethodTransformerBuilder editMethod(String name, String... parameterTypeNames) {
DefaultMethodEditorBuilder builder = new DefaultMethodEditorBuilder(name, parameterTypeNames);
recipeBuilders.add(builder);
return builder;
}
@Override
public ConstructorTransformerBuilder editConstructor(String... parameterTypeNames) {
DefaultMethodEditorBuilder builder = new DefaultMethodEditorBuilder(parameterTypeNames);
recipeBuilders.add(builder);
return builder;
}
@Override
public void weave(String aspectClassName) {
recipes.add(new ClassWeaver(aspectClassName));
}
@Override
public MatchableClassFileTransformer build() {
ClassRecipe recipe = buildClassRecipe();
return new DedicatedClassFileTransformer(pluginContext, targetClassName, recipe);
}
private ClassRecipe buildClassRecipe() {
List<ClassRecipe> recipes = new ArrayList<ClassRecipe>(this.recipes);
for (RecipeBuilder<ClassRecipe> builder : recipeBuilders) {
recipes.add(builder.buildRecipe());
}
if (recipes.isEmpty()) {
throw new IllegalStateException("No class transformation registered");
}
ClassRecipe recipe = recipes.size() == 1 ? recipes.get(0) : new ClassCookBook(recipes);
return recipe;
}
@Override
public ClassRecipe buildRecipe() {
if (condition == null) {
throw new IllegalStateException();
}
ClassRecipe recipe = buildClassRecipe();
return new ConditionalClassRecipe(pluginContext, condition, recipe);
}
private class TargetAnnotatedInterceptorInjectorBuilder implements InterceptorBuilder, RecipeBuilder<ClassRecipe> {
private final String interceptorClassName;
private final Object[] constructorArguments;
private String scopeName;
private ExecutionPolicy executionPoint;
public TargetAnnotatedInterceptorInjectorBuilder(String interceptorClassName, Object[] constructorArguments) {
this.interceptorClassName = interceptorClassName;
this.constructorArguments = constructorArguments;
}
@Override
public void setScope(String scopeName) {
setScope(scopeName, ExecutionPolicy.BOUNDARY);
}
@Override
public void setScope(String scopeName, ExecutionPolicy point) {
this.scopeName = scopeName;
this.executionPoint = point;
}
@Override
public ClassRecipe buildRecipe() {
return new TargetAnnotatedInterceptorInjector(profilerConfig, traceContext, dataSourceMonitorRegistry, apiMetaDataService, pluginContext, interceptorClassName, constructorArguments, scopeName, executionPoint);
}
}
private class AnnotatedInterceptorInjectorBuilder implements InterceptorBuilder, RecipeBuilder<MethodRecipe> {
private final String interceptorClassName;
private final Object[] constructorArguments;
private String scopeName;
private ExecutionPolicy executionPoint;
public AnnotatedInterceptorInjectorBuilder(String interceptorClassName, Object[] constructorArguments) {
this.interceptorClassName = interceptorClassName;
this.constructorArguments = constructorArguments;
}
@Override
public void setScope(String scopeName) {
setScope(scopeName, ExecutionPolicy.BOUNDARY);
}
@Override
public void setScope(String scopeName, ExecutionPolicy point) {
this.scopeName = scopeName;
this.executionPoint = point;
}
@Override
public MethodRecipe buildRecipe() {
return new AnnotatedInterceptorInjector(pluginContext, interceptorClassName, constructorArguments, scopeName, executionPoint);
}
}
public class DefaultMethodEditorBuilder implements MethodTransformerBuilder, ConstructorTransformerBuilder, RecipeBuilder<ClassRecipe> {
private final String methodName;
private final String[] parameterTypeNames;
private final MethodFilter[] filters;
private final List<RecipeBuilder<MethodRecipe>> recipeBuilders = new ArrayList<RecipeBuilder<MethodRecipe>>();
private final EnumSet<MethodTransformerProperty> properties = EnumSet.noneOf(MethodTransformerProperty.class);
private MethodTransformerExceptionHandler exceptionHandler;
private DefaultMethodEditorBuilder(String... parameterTypeNames) {
this.methodName = null;
this.parameterTypeNames = parameterTypeNames;
this.filters = null;
}
private DefaultMethodEditorBuilder(String methodName, String... parameterTypeNames) {
this.methodName = methodName;
this.parameterTypeNames = parameterTypeNames;
this.filters = null;
}
private DefaultMethodEditorBuilder(MethodFilter[] filters) {
this.methodName = null;
this.parameterTypeNames = null;
this.filters = filters;
}
@Override
public void property(MethodTransformerProperty... properties) {
this.properties.addAll(Arrays.asList(properties));
}
@Override
public InterceptorBuilder injectInterceptor(String interceptorClassName, Object... constructorArguments) {
AnnotatedInterceptorInjectorBuilder builder = new AnnotatedInterceptorInjectorBuilder(interceptorClassName, constructorArguments);
recipeBuilders.add(builder);
return builder;
}
@Override
public void exceptionHandler(MethodTransformerExceptionHandler handler) {
this.exceptionHandler = handler;
}
@Override
public MethodTransformer buildRecipe() {
List<MethodRecipe> recipes = buildMethodRecipe();
MethodTransformer transformer = buildMethodEditor(recipes);
return transformer;
}
private MethodTransformer buildMethodEditor(List<MethodRecipe> recipes) {
MethodTransformer transformer;
if (filters != null && filters.length > 0) {
transformer = new FilteringMethodTransformer(filters, recipes, exceptionHandler);
} else if (methodName != null) {
transformer = new DedicatedMethodTransformer(methodName, parameterTypeNames, recipes, exceptionHandler, properties.contains(MethodTransformerProperty.IGNORE_IF_NOT_EXIST));
} else {
transformer = new ConstructorTransformer(parameterTypeNames, recipes, exceptionHandler, properties.contains(MethodTransformerProperty.IGNORE_IF_NOT_EXIST));
}
return transformer;
}
private List<MethodRecipe> buildMethodRecipe() {
if (recipeBuilders.isEmpty()) {
// For now, a method transformer without any interceptor is meaningless.
throw new IllegalStateException("No interceptors are defined");
}
List<MethodRecipe> recipes = new ArrayList<MethodRecipe>(recipeBuilders.size());
for (RecipeBuilder<MethodRecipe> builder : recipeBuilders) {
recipes.add(builder.buildRecipe());
}
return recipes;
}
}
}