org.glowroot.agent.weaving.MessageTemplateImpl 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 2013-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.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.annotations.VisibleForTesting;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.base.Splitter;
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.Lists;
import org.glowroot.agent.shaded.org.checkerframework.checker.nullness.qual.Nullable;
import org.glowroot.agent.shaded.org.glowroot.agent.shaded.org.slf4j.Logger;
import org.glowroot.agent.shaded.org.glowroot.agent.shaded.org.slf4j.LoggerFactory;
import org.glowroot.agent.bytecode.api.MessageTemplate;
import org.glowroot.agent.plugin.api.MethodInfo;
import org.glowroot.agent.shaded.org.glowroot.common.util.Throwables;
import static org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.base.Preconditions.checkNotNull;
public class MessageTemplateImpl implements MessageTemplate {
private static final Logger logger = LoggerFactory.getLogger(MessageTemplateImpl.class);
private static final Pattern pattern = Pattern.compile("\\{\\{([^}]*)}}");
private final ImmutableList allParts;
private final ImmutableList thisPathParts;
private final ImmutableList argPathParts;
private final ImmutableList returnPathParts;
public static MessageTemplateImpl create(String template, MethodInfo methodInfo) {
List allParts = Lists.newArrayList();
List thisPathParts = Lists.newArrayList();
List argPathParts = Lists.newArrayList();
List returnPathParts = Lists.newArrayList();
Matcher matcher = pattern.matcher(template);
int curr = 0;
while (matcher.find()) {
if (matcher.start() > curr) {
allParts.add(new ConstantPart(template.substring(curr, matcher.start())));
}
String group = checkNotNull(matcher.group(1));
String path = group.trim();
int index = path.indexOf('.');
String base;
String remaining;
if (index == -1) {
base = path;
remaining = "";
} else {
base = path.substring(0, index);
remaining = path.substring(index + 1);
}
if (base.equals("this")) {
Class> clazz;
try {
clazz = Class.forName(methodInfo.getDeclaringClassName(), false,
methodInfo.getLoader());
} catch (ClassNotFoundException e) {
logger.debug(e.getMessage(), e);
clazz = null;
}
ValuePathPart part = new ValuePathPart(PartType.THIS_PATH, clazz, remaining);
allParts.add(part);
thisPathParts.add(part);
} else if (base.matches("[0-9]+")) {
int argNumber = Integer.parseInt(base);
List> parameterTypes = methodInfo.getParameterTypes();
if (argNumber < parameterTypes.size()) {
ArgPathPart part =
new ArgPathPart(parameterTypes.get(argNumber), remaining, argNumber);
allParts.add(part);
argPathParts.add(part);
} else {
allParts.add(new ConstantPart(
""));
}
} else if (base.equals("_")) {
ValuePathPart part = new ValuePathPart(PartType.RETURN_PATH,
methodInfo.getReturnType(), remaining);
allParts.add(part);
returnPathParts.add(part);
} else if (base.equals("methodName")) {
allParts.add(new Part(PartType.METHOD_NAME));
} else {
logger.warn("invalid template substitution: {}", path);
allParts.add(new ConstantPart("{{" + path + "}}"));
}
curr = matcher.end();
}
if (curr < template.length()) {
allParts.add(new ConstantPart(template.substring(curr)));
}
return new MessageTemplateImpl(allParts, thisPathParts, argPathParts, returnPathParts);
}
private MessageTemplateImpl(List allParts, List thisPathParts,
List argPathParts, List returnPathParts) {
this.allParts = ImmutableList.copyOf(allParts);
this.thisPathParts = ImmutableList.copyOf(thisPathParts);
this.argPathParts = ImmutableList.copyOf(argPathParts);
this.returnPathParts = ImmutableList.copyOf(returnPathParts);
}
ImmutableList getAllParts() {
return allParts;
}
ImmutableList getThisPathParts() {
return thisPathParts;
}
ImmutableList getArgPathParts() {
return argPathParts;
}
ImmutableList getReturnPathParts() {
return returnPathParts;
}
enum PartType {
CONSTANT, THIS_PATH, ARG_PATH, RETURN_PATH, METHOD_NAME;
}
static class Part {
private final PartType type;
private Part(PartType type) {
this.type = type;
}
PartType getType() {
return type;
}
}
static class ConstantPart extends Part {
private final String constant;
private ConstantPart(String constant) {
super(PartType.CONSTANT);
this.constant = constant;
}
String getConstant() {
return constant;
}
}
static class ValuePathPart extends Part {
private static final char[] hexDigits = "0123456789abcdef".toCharArray();
private final PathEvaluator pathEvaluator;
@VisibleForTesting
ValuePathPart(PartType partType, @Nullable Class> type, String pathAndFormat) {
super(partType);
this.pathEvaluator = PathEvaluator.create(type, pathAndFormat);
}
String evaluatePart(@Nullable Object base) {
if (base == null) {
// this is same as String.valueOf((Object) null);
return "null";
}
try {
return valueOf(pathEvaluator.evaluateOnBase(base));
} catch (InvocationTargetException e) {
logger.debug(e.getMessage(), e);
return "";
} catch (Exception e) {
logger.warn(e.getMessage(), e);
return "";
}
}
private static String valueOf(@Nullable Object value) {
if (value == null) {
return String.valueOf(value);
} else if (value instanceof byte[]) {
return toHex((byte[]) value);
} else if (value instanceof List || value.getClass().isArray()) {
StringBuilder sb = new StringBuilder();
appendValue(sb, value);
return sb.toString();
} else {
// no need for StringBuilder in common case
return String.valueOf(value);
}
}
private static void appendValue(StringBuilder sb, @Nullable Object value) {
if (value == null) {
sb.append(String.valueOf(value));
} else if (value instanceof Iterable) {
appendIterable(sb, (Iterable>) value);
} else if (value.getClass().isArray()) {
appendArray(sb, value);
} else {
sb.append(String.valueOf(value));
}
}
private static void appendIterable(StringBuilder sb, Iterable> items) {
sb.append('[');
boolean comma = false;
for (Object item : items) {
if (comma) {
sb.append(", ");
}
appendValue(sb, item);
comma = true;
}
sb.append(']');
}
private static void appendArray(StringBuilder sb, Object array) {
sb.append('[');
int len = Array.getLength(array);
for (int i = 0; i < len; i++) {
if (i > 0) {
sb.append(", ");
}
appendValue(sb, Array.get(array, i));
}
sb.append(']');
}
private static String toHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(2 + 2 * bytes.length);
sb.append("0x");
for (byte b : bytes) {
// this logic copied from org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.hash.HashCode.toString()
sb.append(hexDigits[(b >> 4) & 0xf]).append(hexDigits[b & 0xf]);
}
return sb.toString();
}
}
static class ArgPathPart extends ValuePathPart {
private final int argNumber;
private ArgPathPart(Class> argClass, String propertyPath, int argNumber) {
super(PartType.ARG_PATH, argClass, propertyPath);
this.argNumber = argNumber;
}
int getArgNumber() {
return argNumber;
}
}
@VisibleForTesting
static class PathEvaluator {
private static final Splitter splitter = Splitter.on('.').omitEmptyStrings();
private final Accessor[] accessors;
private final List remainingPath;
private final @Nullable String format;
private final @Nullable String formatArg;
static PathEvaluator create(@Nullable Class> type, String pathAndFormat) {
String path;
String format;
String formatArg;
int index = pathAndFormat.indexOf('|');
if (index == -1) {
path = pathAndFormat;
format = null;
formatArg = null;
} else {
// trim is to allow spaces on either side of the "|"
path = pathAndFormat.substring(0, index).trim();
String formatAndArg = pathAndFormat.substring(index + 1).trim();
index = formatAndArg.indexOf(':');
if (index == -1) {
format = formatAndArg;
formatArg = null;
} else {
format = formatAndArg.substring(0, index);
formatArg = formatAndArg.substring(index + 1);
}
}
List accessors = Lists.newArrayList();
if (type == null) {
return new PathEvaluator(accessors, splitter.splitToList(path), format, formatArg);
}
List parts = Lists.newArrayList(splitter.split(path));
Class> currType = type;
while (!parts.isEmpty()) {
String currPart = parts.remove(0);
Accessor accessor = Beans.loadPossiblyArrayBasedAccessor(currType, currPart);
if (accessor == null) {
parts.add(0, currPart);
break;
}
accessors.add(accessor);
currType = accessor.getValueType();
}
return new PathEvaluator(accessors, parts, format, formatArg);
}
private PathEvaluator(List accessors, List remainingPath,
@Nullable String format, @Nullable String formatArg) {
this.accessors = accessors.toArray(new Accessor[accessors.size()]);
this.remainingPath = remainingPath;
this.format = format;
this.formatArg = formatArg;
}
@Nullable
Object evaluateOnBase(Object base) throws Exception {
Object curr = base;
for (Accessor accessor : accessors) {
curr = accessor.evaluate(curr);
if (curr == null) {
return null;
}
}
if (!remainingPath.isEmpty()) {
// too bad, revert to slow Beans
curr = Beans.value(curr, remainingPath);
}
if ("charset".equals(format) && formatArg != null && curr instanceof byte[]) {
if (formatArg.equals("default")) {
return new String((byte[]) curr);
} else {
return new String((byte[]) curr, formatArg);
}
}
return curr;
}
}
}