org.glowroot.agent.weaving.AdviceBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of glowroot-agent-it-harness Show documentation
Show all versions of glowroot-agent-it-harness Show documentation
Glowroot Agent Integration Test Harness
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.glowroot.agent.weaving;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.base.Joiner;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.ImmutableList;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.ImmutableMap;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.Lists;
import org.glowroot.agent.shaded.org.checkerframework.checker.nullness.qual.Nullable;
import org.glowroot.agent.shaded.org.objectweb.asm.Type;
import org.glowroot.agent.shaded.org.objectweb.asm.commons.Method;
import org.glowroot.agent.plugin.api.OptionalThreadContext;
import org.glowroot.agent.plugin.api.ThreadContext;
import org.glowroot.agent.plugin.api.weaving.BindClassMeta;
import org.glowroot.agent.plugin.api.weaving.BindMethodMeta;
import org.glowroot.agent.plugin.api.weaving.BindMethodName;
import org.glowroot.agent.plugin.api.weaving.BindOptionalReturn;
import org.glowroot.agent.plugin.api.weaving.BindParameter;
import org.glowroot.agent.plugin.api.weaving.BindParameterArray;
import org.glowroot.agent.plugin.api.weaving.BindReceiver;
import org.glowroot.agent.plugin.api.weaving.BindReturn;
import org.glowroot.agent.plugin.api.weaving.BindThrowable;
import org.glowroot.agent.plugin.api.weaving.BindTraveler;
import org.glowroot.agent.plugin.api.weaving.IsEnabled;
import org.glowroot.agent.plugin.api.weaving.OnAfter;
import org.glowroot.agent.plugin.api.weaving.OnBefore;
import org.glowroot.agent.plugin.api.weaving.OnReturn;
import org.glowroot.agent.plugin.api.weaving.OnThrow;
import org.glowroot.agent.plugin.api.weaving.Pointcut;
import org.glowroot.agent.util.MaybePatterns;
import org.glowroot.agent.weaving.Advice.AdviceParameter;
import org.glowroot.agent.weaving.Advice.ParameterKind;
import org.glowroot.agent.weaving.ClassLoaders.LazyDefinedClass;
import org.glowroot.agent.weaving.PluginDetail.PointcutClass;
import org.glowroot.agent.weaving.PluginDetail.PointcutMethod;
import static org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.base.Preconditions.checkNotNull;
class AdviceBuilder {
private static final Type IsEnabledType = Type.getType(IsEnabled.class);
private static final Type OnBeforeType = Type.getType(OnBefore.class);
private static final Type OnReturnType = Type.getType(OnReturn.class);
private static final Type OnThrowType = Type.getType(OnThrow.class);
private static final Type OnAfterType = Type.getType(OnAfter.class);
private static final Type ThreadContextType = Type.getType(ThreadContext.class);
private static final Type OptionalThreadContextType = Type.getType(OptionalThreadContext.class);
private static final Type BindReceiverType = Type.getType(BindReceiver.class);
private static final Type BindParameterType = Type.getType(BindParameter.class);
private static final Type BindParameterArrayType = Type.getType(BindParameterArray.class);
private static final Type BindMethodNameType = Type.getType(BindMethodName.class);
private static final Type BindReturnType = Type.getType(BindReturn.class);
private static final Type BindOptionalReturnType = Type.getType(BindOptionalReturn.class);
private static final Type BindThrowableType = Type.getType(BindThrowable.class);
private static final Type BindTravelerType = Type.getType(BindTraveler.class);
private static final Type BindClassMetaType = Type.getType(BindClassMeta.class);
private static final Type BindMethodMetaType = Type.getType(BindMethodMeta.class);
private static final Type StringType = Type.getType(String.class);
private static final Type ThrowableType = Type.getType(Throwable.class);
private static final ImmutableList isEnabledBindAnnotationTypes =
ImmutableList.of(BindReceiverType, BindParameterType, BindParameterArrayType,
BindMethodNameType, BindClassMetaType, BindMethodMetaType);
private static final ImmutableList onBeforeBindAnnotationTypes =
ImmutableList.of(BindReceiverType, BindParameterType, BindParameterArrayType,
BindMethodNameType, BindClassMetaType, BindMethodMetaType);
private static final ImmutableList onReturnBindAnnotationTypes =
ImmutableList.of(BindReceiverType, BindParameterType, BindParameterArrayType,
BindMethodNameType, BindReturnType, BindOptionalReturnType, BindTravelerType,
BindClassMetaType, BindMethodMetaType);
private static final ImmutableList onThrowBindAnnotationTypes =
ImmutableList.of(BindReceiverType, BindParameterType, BindParameterArrayType,
BindMethodNameType, BindThrowableType, BindTravelerType, BindClassMetaType,
BindMethodMetaType);
private static final ImmutableList onAfterBindAnnotationTypes =
ImmutableList.of(BindReceiverType, BindParameterType, BindParameterArrayType,
BindMethodNameType, BindTravelerType, BindClassMetaType, BindMethodMetaType);
private static final ImmutableMap parameterKindMap =
new ImmutableMap.Builder()
.put(BindReceiverType, ParameterKind.RECEIVER)
.put(BindParameterType, ParameterKind.METHOD_ARG)
.put(BindParameterArrayType, ParameterKind.METHOD_ARG_ARRAY)
.put(BindMethodNameType, ParameterKind.METHOD_NAME)
.put(BindReturnType, ParameterKind.RETURN)
.put(BindOptionalReturnType, ParameterKind.OPTIONAL_RETURN)
.put(BindThrowableType, ParameterKind.THROWABLE)
.put(BindTravelerType, ParameterKind.TRAVELER)
.put(BindClassMetaType, ParameterKind.CLASS_META)
.put(BindMethodMetaType, ParameterKind.METHOD_META)
.build();
private final ImmutableAdvice.Builder builder = ImmutableAdvice.builder();
private final @Nullable PointcutClass adviceClass;
private final @Nullable LazyDefinedClass lazyAdviceClass;
private boolean hasIsEnabledAdvice;
private boolean hasOnBeforeAdvice;
private boolean hasOnReturnAdvice;
private boolean hasOnThrowAdvice;
private boolean hasOnAfterAdvice;
AdviceBuilder(PointcutClass adviceClass) {
this.adviceClass = adviceClass;
this.lazyAdviceClass = null;
builder.reweavable(false);
}
AdviceBuilder(LazyDefinedClass lazyAdviceClass, boolean reweavable) {
this.adviceClass = null;
this.lazyAdviceClass = lazyAdviceClass;
builder.reweavable(reweavable);
}
Advice build() throws Exception {
PointcutClass adviceClass = this.adviceClass;
if (adviceClass == null) {
// safe check, if adviceClass is null then lazyAdviceClass is non-null
checkNotNull(lazyAdviceClass);
adviceClass = PluginDetailBuilder.buildAdviceClass(lazyAdviceClass.bytes());
}
Pointcut pointcut = adviceClass.pointcut();
checkNotNull(pointcut, "Class has no @Pointcut annotation");
builder.pointcut(pointcut);
builder.adviceType(adviceClass.type());
builder.pointcutClassNamePattern(MaybePatterns.buildPattern(pointcut.className()));
builder.pointcutClassAnnotationPattern(
MaybePatterns.buildPattern(pointcut.classAnnotation()));
builder.pointcutSubTypeRestrictionPattern(
MaybePatterns.buildPattern(pointcut.subTypeRestriction()));
builder.pointcutSuperTypeRestrictionPattern(
MaybePatterns.buildPattern(pointcut.superTypeRestriction()));
builder.pointcutMethodNamePattern(MaybePatterns.buildPattern(pointcut.methodName()));
builder.pointcutMethodAnnotationPattern(
MaybePatterns.buildPattern(pointcut.methodAnnotation()));
builder.pointcutMethodParameterTypes(buildPatterns(pointcut.methodParameterTypes()));
// hasBindThreadContext will be overridden below if needed
builder.hasBindThreadContext(false);
// hasBindOptionalThreadContext will be overridden below if needed
builder.hasBindOptionalThreadContext(false);
for (PointcutMethod adviceMethod : adviceClass.methods()) {
if (adviceMethod.annotationTypes().contains(IsEnabledType)) {
initIsEnabledAdvice(adviceClass, adviceMethod);
} else if (adviceMethod.annotationTypes().contains(OnBeforeType)) {
initOnBeforeAdvice(adviceClass, adviceMethod);
} else if (adviceMethod.annotationTypes().contains(OnReturnType)) {
initOnReturnAdvice(adviceClass, adviceMethod);
} else if (adviceMethod.annotationTypes().contains(OnThrowType)) {
initOnThrowAdvice(adviceClass, adviceMethod);
} else if (adviceMethod.annotationTypes().contains(OnAfterType)) {
initOnAfterAdvice(adviceClass, adviceMethod);
}
}
if (adviceClass.collocateInClassLoader()) {
PluginClassRenamer pluginClassRenamer = new PluginClassRenamer(adviceClass);
builder.nonBootstrapLoaderAdviceClass(
pluginClassRenamer.buildNonBootstrapLoaderAdviceClass());
builder.nonBootstrapLoaderAdvice(
pluginClassRenamer.buildNonBootstrapLoaderAdvice(builder.build()));
}
Advice advice = builder.build();
if (pointcut.methodName().equals("") && advice.onBeforeAdvice() != null
&& advice.hasBindOptionalThreadContext()) {
// this is because of the way @OnBefore advice is handled on constructors,
// see WeavingMethodVisitory.invokeOnBefore()
throw new IllegalStateException("@BindOptionalThreadContext is not allowed in a"
+ " @Pointcut with methodName \"\" that has an @OnBefore method");
}
if (pointcut.methodName().equals("") && advice.isEnabledAdvice() != null) {
for (AdviceParameter parameter : advice.isEnabledParameters()) {
if (parameter.kind() == ParameterKind.RECEIVER) {
// @IsEnabled is called before the super constructor is called, so "this" is not
// available yet
throw new IllegalStateException("@BindReceiver is not allowed on @IsEnabled for"
+ " a @Pointcut with methodName \"\"");
}
}
}
return advice;
}
private void initIsEnabledAdvice(PointcutClass adviceClass, PointcutMethod adviceMethod)
throws AdviceConstructionException {
checkState(!hasIsEnabledAdvice, "@Pointcut '" + adviceClass.type().getClassName()
+ "' has more than one @IsEnabled method");
Method asmMethod = adviceMethod.toAsmMethod();
checkState(asmMethod.getReturnType().getSort() == Type.BOOLEAN,
"@IsEnabled method must return boolean");
builder.isEnabledAdvice(asmMethod);
List parameters =
getAdviceParameters(adviceMethod.parameterAnnotationTypes(),
asmMethod.getArgumentTypes(), isEnabledBindAnnotationTypes, IsEnabledType);
builder.addAllIsEnabledParameters(parameters);
hasIsEnabledAdvice = true;
}
private void initOnBeforeAdvice(PointcutClass adviceClass, PointcutMethod adviceMethod)
throws AdviceConstructionException {
checkState(!hasOnBeforeAdvice, "@Pointcut '" + adviceClass.type().getClassName()
+ "' has more than one @OnBefore method");
Method asmMethod = adviceMethod.toAsmMethod();
builder.onBeforeAdvice(asmMethod);
List parameters =
getAdviceParameters(adviceMethod.parameterAnnotationTypes(),
asmMethod.getArgumentTypes(), onBeforeBindAnnotationTypes, OnBeforeType);
builder.addAllOnBeforeParameters(parameters);
if (asmMethod.getReturnType().getSort() != Type.VOID) {
builder.travelerType(asmMethod.getReturnType());
}
checkForBindThreadContext(parameters);
checkForBindOptionalThreadContext(parameters);
hasOnBeforeAdvice = true;
}
private void initOnReturnAdvice(PointcutClass adviceClass, PointcutMethod adviceMethod)
throws AdviceConstructionException {
checkState(!hasOnReturnAdvice, "@Pointcut '" + adviceClass.type().getClassName()
+ "' has more than one @OnReturn method");
Method asmMethod = adviceMethod.toAsmMethod();
List parameters =
getAdviceParameters(adviceMethod.parameterAnnotationTypes(),
asmMethod.getArgumentTypes(), onReturnBindAnnotationTypes, OnReturnType);
for (int i = 1; i < parameters.size(); i++) {
checkState(parameters.get(i).kind() != ParameterKind.RETURN,
"@BindReturn must be the first argument to @OnReturn");
checkState(parameters.get(i).kind() != ParameterKind.OPTIONAL_RETURN,
"@BindOptionalReturn must be the first argument to @OnReturn");
}
builder.onReturnAdvice(asmMethod);
builder.addAllOnReturnParameters(parameters);
checkForBindThreadContext(parameters);
checkForBindOptionalThreadContext(parameters);
hasOnReturnAdvice = true;
}
private void initOnThrowAdvice(PointcutClass adviceClass, PointcutMethod adviceMethod)
throws AdviceConstructionException {
checkState(!hasOnThrowAdvice, "@Pointcut '" + adviceClass.type().getClassName()
+ "' has more than one @OnThrow method");
Method asmMethod = adviceMethod.toAsmMethod();
List parameters =
getAdviceParameters(adviceMethod.parameterAnnotationTypes(),
asmMethod.getArgumentTypes(), onThrowBindAnnotationTypes, OnThrowType);
for (int i = 1; i < parameters.size(); i++) {
checkState(parameters.get(i).kind() != ParameterKind.THROWABLE,
"@BindThrowable must be the first argument to @OnThrow");
}
checkState(asmMethod.getReturnType().getSort() == Type.VOID,
"@OnThrow method must return void (for now)");
builder.onThrowAdvice(asmMethod);
builder.addAllOnThrowParameters(parameters);
checkForBindThreadContext(parameters);
checkForBindOptionalThreadContext(parameters);
hasOnThrowAdvice = true;
}
private void initOnAfterAdvice(PointcutClass adviceClass, PointcutMethod adviceMethod)
throws AdviceConstructionException {
checkState(!hasOnAfterAdvice, "@Pointcut '" + adviceClass.type().getClassName()
+ "' has more than one @OnAfter method");
Method asmMethod = adviceMethod.toAsmMethod();
checkState(asmMethod.getReturnType().getSort() == Type.VOID,
"@OnAfter method must return void");
builder.onAfterAdvice(asmMethod);
List parameters =
getAdviceParameters(adviceMethod.parameterAnnotationTypes(),
asmMethod.getArgumentTypes(), onAfterBindAnnotationTypes, OnAfterType);
builder.addAllOnAfterParameters(parameters);
checkForBindThreadContext(parameters);
checkForBindOptionalThreadContext(parameters);
hasOnAfterAdvice = true;
}
private void checkForBindThreadContext(List parameters) {
for (AdviceParameter parameter : parameters) {
if (parameter.kind() == ParameterKind.THREAD_CONTEXT) {
builder.hasBindThreadContext(true);
return;
}
}
}
private void checkForBindOptionalThreadContext(List parameters) {
for (AdviceParameter parameter : parameters) {
if (parameter.kind() == ParameterKind.OPTIONAL_THREAD_CONTEXT) {
builder.hasBindOptionalThreadContext(true);
break;
}
}
}
private static void checkState(boolean condition, String message)
throws AdviceConstructionException {
if (!condition) {
throw new AdviceConstructionException(message);
}
}
private static List