
org.glowroot.agent.impl.AdviceCache Maven / Gradle / Ivy
/*
* Copyright 2012-2015 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.impl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.Instrumentation;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import org.glowroot.agent.shaded.google.common.annotations.VisibleForTesting;
import org.glowroot.agent.shaded.google.common.base.Supplier;
import org.glowroot.agent.shaded.google.common.collect.ImmutableList;
import org.glowroot.agent.shaded.google.common.collect.ImmutableMap;
import org.glowroot.agent.shaded.google.common.collect.ImmutableSet;
import org.glowroot.agent.shaded.google.common.collect.Iterables;
import org.glowroot.agent.shaded.google.common.collect.Lists;
import org.glowroot.agent.shaded.google.common.collect.Maps;
import org.glowroot.agent.shaded.google.common.collect.Sets;
import org.glowroot.agent.shaded.google.common.io.ByteStreams;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.glowroot.agent.shaded.slf4j.Logger;
import org.glowroot.agent.shaded.slf4j.LoggerFactory;
import org.glowroot.agent.advicegen.AdviceGenerator;
import org.glowroot.agent.plugin.api.weaving.Mixin;
import org.glowroot.agent.plugin.api.weaving.Pointcut;
import org.glowroot.agent.plugin.api.weaving.Shim;
import org.glowroot.agent.weaving.Advice;
import org.glowroot.agent.weaving.AdviceBuilder;
import org.glowroot.agent.weaving.ClassLoaders;
import org.glowroot.agent.weaving.ClassLoaders.LazyDefinedClass;
import org.glowroot.agent.weaving.MixinType;
import org.glowroot.agent.weaving.ShimType;
import org.glowroot.common.config.InstrumentationConfig;
import org.glowroot.common.config.PluginDescriptor;
import org.glowroot.common.util.OnlyUsedByTests;
import static org.glowroot.agent.shaded.google.common.base.Preconditions.checkNotNull;
public class AdviceCache {
private static final Logger logger = LoggerFactory.getLogger(AdviceCache.class);
private static final AtomicInteger jarFileCounter = new AtomicInteger();
private final ImmutableList pluginAdvisors;
private final ImmutableList shimTypes;
private final ImmutableList mixinTypes;
private final @Nullable Instrumentation instrumentation;
private final File baseDir;
private volatile ImmutableList reweavableAdvisors;
private volatile ImmutableSet reweavableConfigVersions;
private volatile ImmutableList allAdvisors;
public AdviceCache(List pluginDescriptors, List pluginJars,
List reweavableConfigs,
@Nullable Instrumentation instrumentation, File baseDir) throws Exception {
List pluginAdvisors = Lists.newArrayList();
List shimTypes = Lists.newArrayList();
List mixinTypes = Lists.newArrayList();
Map lazyAdvisors = Maps.newHashMap();
// use temporary class loader so @Pointcut classes won't be defined for real until
// PointcutClassVisitor is ready to weave them
final URL[] pluginJarURLs = new URL[pluginJars.size()];
for (int i = 0; i < pluginJars.size(); i++) {
pluginJarURLs[i] = pluginJars.get(i).toURI().toURL();
}
ClassLoader tempIsolatedClassLoader = new IsolatedClassLoader(pluginJarURLs);
for (PluginDescriptor pluginDescriptor : pluginDescriptors) {
for (String aspect : pluginDescriptor.aspects()) {
try {
Class> aspectClass = Class.forName(aspect, false, tempIsolatedClassLoader);
pluginAdvisors.addAll(getAdvisors(aspectClass));
shimTypes.addAll(getShimTypes(aspectClass));
mixinTypes.addAll(getMixinTypes(aspectClass));
} catch (ClassNotFoundException e) {
logger.warn("aspect not found: {}", aspect, e);
}
}
lazyAdvisors.putAll(AdviceGenerator.createAdvisors(
pluginDescriptor.instrumentationConfigs(), pluginDescriptor.id(), false));
}
for (Entry entry : lazyAdvisors.entrySet()) {
pluginAdvisors.add(entry.getKey());
}
if (instrumentation == null) {
// this is for tests that don't run with javaagent container
ClassLoader loader = Thread.currentThread().getContextClassLoader();
checkNotNull(loader, "Context class loader must be set");
ClassLoaders.defineClassesInClassLoader(lazyAdvisors.values(), loader);
} else {
File generatedJarDir = new File(baseDir, "tmp");
ClassLoaders.createDirectoryOrCleanPreviousContentsWithPrefix(generatedJarDir,
"plugin-pointcuts.jar");
if (!lazyAdvisors.isEmpty()) {
File jarFile = new File(generatedJarDir, "plugin-pointcuts.jar");
ClassLoaders.defineClassesInBootstrapClassLoader(lazyAdvisors.values(),
instrumentation, jarFile);
}
}
this.pluginAdvisors = ImmutableList.copyOf(pluginAdvisors);
this.shimTypes = ImmutableList.copyOf(shimTypes);
this.mixinTypes = ImmutableList.copyOf(mixinTypes);
this.instrumentation = instrumentation;
this.baseDir = baseDir;
updateAdvisors(reweavableConfigs, true);
}
public Supplier> getAdvisorsSupplier() {
return new Supplier>() {
@Override
public List get() {
return allAdvisors;
}
};
}
@VisibleForTesting
public List getShimTypes() {
return shimTypes;
}
@VisibleForTesting
public List getMixinTypes() {
return mixinTypes;
}
@EnsuresNonNull({"reweavableAdvisors", "reweavableConfigVersions", "allAdvisors"})
public void updateAdvisors(/*>>>@UnknownInitialization(AdviceCache.class) AdviceCache this,*/
List reweavableConfigs, boolean cleanTmpDir) throws Exception {
ImmutableMap advisors =
AdviceGenerator.createAdvisors(reweavableConfigs, null, true);
if (instrumentation == null) {
// this is for tests that don't run with javaagent container
ClassLoader loader = Thread.currentThread().getContextClassLoader();
checkNotNull(loader, "Context class loader must be set");
ClassLoaders.defineClassesInClassLoader(advisors.values(), loader);
} else {
File generatedJarDir = new File(baseDir, "tmp");
if (cleanTmpDir) {
ClassLoaders.createDirectoryOrCleanPreviousContentsWithPrefix(generatedJarDir,
"config-pointcuts");
}
if (!advisors.isEmpty()) {
String suffix = "";
int count = jarFileCounter.incrementAndGet();
if (count > 1) {
suffix = "-" + count;
}
File jarFile = new File(generatedJarDir, "config-pointcuts" + suffix + ".jar");
ClassLoaders.defineClassesInBootstrapClassLoader(advisors.values(), instrumentation,
jarFile);
}
}
reweavableAdvisors = advisors.keySet().asList();
reweavableConfigVersions = createReweavableConfigVersions(reweavableConfigs);
allAdvisors = ImmutableList.copyOf(Iterables.concat(pluginAdvisors, reweavableAdvisors));
}
public boolean isOutOfSync(List reweavableConfigs) {
Set versions = Sets.newHashSet();
for (InstrumentationConfig reweavableConfig : reweavableConfigs) {
versions.add(reweavableConfig.version());
}
return !versions.equals(this.reweavableConfigVersions);
}
private static List getAdvisors(Class> aspectClass) {
List advisors = Lists.newArrayList();
for (Class> memberClass : aspectClass.getClasses()) {
if (memberClass.isAnnotationPresent(Pointcut.class)) {
try {
advisors.add(new AdviceBuilder(memberClass).build());
} catch (Exception e) {
logger.error("error creating advice: {}", memberClass.getName(), e);
}
}
}
return advisors;
}
private static List getShimTypes(Class> aspectClass) throws IOException {
List shimTypes = Lists.newArrayList();
for (Class> memberClass : aspectClass.getClasses()) {
Shim shim = memberClass.getAnnotation(Shim.class);
if (shim != null) {
shimTypes.add(ShimType.from(shim, memberClass));
}
}
return shimTypes;
}
private static List getMixinTypes(Class> aspectClass) throws IOException {
List mixinTypes = Lists.newArrayList();
for (Class> memberClass : aspectClass.getClasses()) {
Mixin mixin = memberClass.getAnnotation(Mixin.class);
if (mixin != null) {
mixinTypes.add(MixinType.from(mixin, memberClass));
}
}
return mixinTypes;
}
private static ImmutableSet createReweavableConfigVersions(
List reweavableConfigs) {
Set versions = Sets.newHashSet();
for (InstrumentationConfig reweavableConfig : reweavableConfigs) {
versions.add(reweavableConfig.version());
}
return ImmutableSet.copyOf(versions);
}
// this method exists because tests cannot use (sometimes) shaded guava Supplier
@OnlyUsedByTests
public List getAdvisors() {
return getAdvisorsSupplier().get();
}
private static class IsolatedClassLoader extends URLClassLoader {
private IsolatedClassLoader(URL[] urls) {
super(urls);
}
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
if (useBootstrapClassLoader(name)) {
return super.findClass(name);
}
String resourceName = name.replace('.', '/') + ".class";
InputStream input = getResourceAsStream(resourceName);
if (input == null) {
throw new ClassNotFoundException(name);
}
byte[] b;
try {
b = ByteStreams.toByteArray(input);
} catch (IOException e) {
throw new IllegalStateException(e);
}
int lastIndexOf = name.lastIndexOf('.');
if (lastIndexOf != -1) {
String packageName = name.substring(0, lastIndexOf);
createPackageIfNecessary(packageName);
}
return defineClass(name, b, 0, b.length);
}
@Override
protected synchronized Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
if (useBootstrapClassLoader(name)) {
return super.loadClass(name, resolve);
}
return findClass(name);
}
private boolean useBootstrapClassLoader(String name) {
return name.startsWith("java.") || name.startsWith("sun.")
|| name.startsWith("javax.management.")
|| name.startsWith("org.glowroot.agent.plugin.api.");
}
private void createPackageIfNecessary(String packageName) {
if (getPackage(packageName) == null) {
definePackage(packageName, null, null, null, null, null, null, null);
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy