package org.stagemonitor.core.instrument;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.utility.JavaModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stagemonitor.core.CorePlugin;
import org.stagemonitor.core.Stagemonitor;
import org.stagemonitor.configuration.ConfigurationRegistry;
import java.lang.annotation.Annotation;
import java.security.ProtectionDomain;
import java.util.Collections;
import java.util.List;
import static net.bytebuddy.matcher.ElementMatchers.any;
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.isFinal;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isNative;
import static net.bytebuddy.matcher.ElementMatchers.isSynthetic;
import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer;
import static net.bytebuddy.matcher.ElementMatchers.none;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static org.stagemonitor.core.instrument.CachedClassLoaderMatcher.cached;
import static org.stagemonitor.core.instrument.StagemonitorClassNameMatcher.isInsideMonitoredProject;
import static org.stagemonitor.core.instrument.TimedElementMatcherDecorator.timed;
public abstract class StagemonitorByteBuddyTransformer {
protected static final ConfigurationRegistry configuration = Stagemonitor.getConfiguration();
protected static final boolean DEBUG_INSTRUMENTATION = configuration.getConfig(CorePlugin.class).isDebugInstrumentation();
private static final Logger logger = LoggerFactory.getLogger(StagemonitorByteBuddyTransformer.class);
private static final ElementMatcher.Junction<ClassLoader> applicationClassLoaderMatcher = cached(new ApplicationClassLoaderMatcher());
protected final String transformerName = getClass().getSimpleName();
public final AgentBuilder.RawMatcher getMatcher() {
return new AgentBuilder.RawMatcher() {
@Override
public boolean matches(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule, Class<?> classBeingRedefined, ProtectionDomain protectionDomain) {
final boolean matches = timed("type", transformerName, getTypeMatcher()).matches(typeDescription) &&
getRawMatcher().matches(typeDescription, classLoader, javaModule, classBeingRedefined, protectionDomain) &&
timed("classloader", "application", getClassLoaderMatcher()).matches(classLoader);
if (!matches) {
onIgnored(typeDescription, classLoader);
}
return matches;
}
};
}
protected AgentBuilder.RawMatcher getRawMatcher() {
return NoOpRawMatcher.INSTANCE;
}
protected ElementMatcher.Junction<TypeDescription> getTypeMatcher() {
return getIncludeTypeMatcher()
.and(not(isInterface()))
.and(not(isSynthetic()))
.and(not(getExtraExcludeTypeMatcher()));
}
public boolean isActive() {
return true;
}
protected ElementMatcher.Junction<TypeDescription> getIncludeTypeMatcher() {
return isInsideMonitoredProject().or(getExtraIncludeTypeMatcher()).and(getNarrowTypesMatcher());
}
protected ElementMatcher.Junction<TypeDescription> getExtraIncludeTypeMatcher() {
return none();
}
protected ElementMatcher.Junction<TypeDescription> getNarrowTypesMatcher() {
return any();
}
protected ElementMatcher.Junction<TypeDescription> getExtraExcludeTypeMatcher() {
return none();
}
protected ElementMatcher.Junction<ClassLoader> getClassLoaderMatcher() {
return applicationClassLoaderMatcher;
}
public AgentBuilder.Transformer getTransformer() {
final AsmVisitorWrapper.ForDeclaredMethods advice = getAdvice();
if (advice == null) {
return AgentBuilder.Transformer.NoOp.INSTANCE;
} else {
return new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(DynamicType.Builder<?> builder, TypeDescription typeDescription,
ClassLoader classLoader, JavaModule module) {
beforeTransformation(typeDescription, classLoader);
return builder.visit(advice);
}
};
}
}
private AsmVisitorWrapper.ForDeclaredMethods getAdvice() {
try {
return registerDynamicValues()
.to(getAdviceClass())
.on(timed("method", transformerName, getMethodElementMatcher()));
} catch (NoClassDefFoundError error) {
logger.debug("Error while creating advice. This usually means that a optional type is not present " +
"so this is nothing wo worry about. Error message: {}", error.getMessage());
return null;
}
}
private Advice.WithCustomMapping registerDynamicValues() {
List<StagemonitorDynamicValue<?>> dynamicValues = getDynamicValues();
Advice.WithCustomMapping withCustomMapping = Advice.withCustomMapping();
for (StagemonitorDynamicValue dynamicValue : dynamicValues) {
withCustomMapping = withCustomMapping.bind(dynamicValue.getAnnotationClass(), dynamicValue);
}
return withCustomMapping;
}
protected List<StagemonitorDynamicValue<?>> getDynamicValues() {
return Collections.emptyList();
}
protected ElementMatcher.Junction<MethodDescription> getMethodElementMatcher() {
return not(isConstructor())
.and(not(isAbstract()))
.and(not(isNative()))
.and(not(isFinal()))
.and(not(isSynthetic()))
.and(not(isTypeInitializer()))
.and(getExtraMethodElementMatcher());
}
protected ElementMatcher.Junction<MethodDescription> getExtraMethodElementMatcher() {
return any();
}
protected Class<? extends StagemonitorByteBuddyTransformer> getAdviceClass() {
return getClass();
}
public static abstract class StagemonitorDynamicValue<T extends Annotation> extends Advice.DynamicValue.ForFixedValue<T> {
public abstract Class<T> getAnnotationClass();
}
/**
* Returns the order of this transformer when multiple transformers match a method.
* </p>
* Higher orders will be applied first
*
* @return the order
*/
protected int getOrder() {
return 0;
}
/**
* This method is called before the transformation.
* You can stop the transformation from happening by returning false from this method.
*
* @param typeDescription The type that is being transformed.
* @param classLoader The class loader which is loading this type.
* @return <code>true</code> to proceed with the transformation, <code>false</code> to stop this transformation
*/
public void beforeTransformation(TypeDescription typeDescription, ClassLoader classLoader) {
if (DEBUG_INSTRUMENTATION && logger.isDebugEnabled()) {
logger.debug("TRANSFORM {} ({})", typeDescription.getName(), getClass().getSimpleName());
}
}
public void onIgnored(TypeDescription typeDescription, ClassLoader classLoader) {
if (DEBUG_INSTRUMENTATION && logger.isDebugEnabled() && getTypeMatcher().matches(typeDescription)) {
logger.debug("IGNORE {} ({})", typeDescription.getName(), getClass().getSimpleName());
}
}
private static class NoOpRawMatcher implements AgentBuilder.RawMatcher {
public static final NoOpRawMatcher INSTANCE = new NoOpRawMatcher();
@Override
public boolean matches(TypeDescription typeDescription, ClassLoader classLoader, JavaModule javaModule, Class<?> classBeingRedefined, ProtectionDomain protectionDomain) {
return true;
}
}
}