/*
* Copyright 2014 NAVER Corp.
*
* 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 com.navercorp.pinpoint.profiler;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import com.google.inject.Inject;
import com.navercorp.pinpoint.bootstrap.config.ProfilerConfig;
import com.navercorp.pinpoint.bootstrap.instrument.DynamicTransformTrigger;
import com.navercorp.pinpoint.profiler.instrument.InstrumentEngine;
import com.navercorp.pinpoint.profiler.instrument.transformer.DebugTransformerRegistry;
import com.navercorp.pinpoint.profiler.instrument.transformer.TransformerRegistry;
import com.navercorp.pinpoint.profiler.plugin.PluginContextLoadResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.navercorp.pinpoint.profiler.instrument.transformer.DefaultTransformerRegistry;
import com.navercorp.pinpoint.profiler.plugin.MatchableClassFileTransformer;
import com.navercorp.pinpoint.profiler.util.JavaAssistUtils;
/**
* @author emeroad
* @author netspider
* @author jaehong.kim
*/
public class DefaultClassFileTransformerDispatcher implements ClassFileTransformerDispatcher {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final boolean isDebug = logger.isDebugEnabled();
private final ClassLoader agentClassLoader = this.getClass().getClassLoader();
private final TransformerRegistry transformerRegistry;
private final DynamicTransformerRegistry dynamicTransformerRegistry;
private final TransformerRegistry debugTransformerRegistry;
private final ClassFileFilter pinpointClassFilter;
private final ClassFileFilter unmodifiableFilter;
@Inject
public DefaultClassFileTransformerDispatcher(ProfilerConfig profilerConfig, PluginContextLoadResult pluginContextLoadResult, InstrumentEngine instrumentEngine,
DynamicTransformTrigger dynamicTransformTrigger, DynamicTransformerRegistry dynamicTransformerRegistry) {
if (profilerConfig == null) {
throw new NullPointerException("profilerConfig must not be null");
}
if (pluginContextLoadResult == null) {
throw new NullPointerException("pluginContexts must not be null");
}
if (instrumentEngine == null) {
throw new NullPointerException("instrumentEngine must not be null");
}
if (dynamicTransformerRegistry == null) {
throw new NullPointerException("dynamicTransformerRegistry must not be null");
}
this.debugTransformerRegistry = new DebugTransformerRegistry(profilerConfig, instrumentEngine, dynamicTransformTrigger);
this.pinpointClassFilter = new PinpointClassFilter(agentClassLoader);
this.unmodifiableFilter = new UnmodifiableClassFilter();
this.transformerRegistry = createTransformerRegistry(pluginContextLoadResult);
this.dynamicTransformerRegistry = dynamicTransformerRegistry;
}
@Override
public byte[] transform(ClassLoader classLoader, String classInternalName, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classFileBuffer) throws IllegalClassFormatException {
if (!pinpointClassFilter.accept(classLoader, classInternalName, classBeingRedefined, protectionDomain, classFileBuffer)) {
return null;
}
final ClassFileTransformer dynamicTransformer = dynamicTransformerRegistry.getTransformer(classLoader, classInternalName);
if (dynamicTransformer != null) {
return transform0(classLoader, classInternalName, classBeingRedefined, protectionDomain, classFileBuffer, dynamicTransformer);
}
if (!unmodifiableFilter.accept(classLoader, classInternalName, classBeingRedefined, protectionDomain, classFileBuffer)) {
return null;
}
ClassFileTransformer transformer = this.transformerRegistry.findTransformer(classInternalName);
if (transformer == null) {
// For debug
// TODO What if a modifier is duplicated?
transformer = this.debugTransformerRegistry.findTransformer(classInternalName);
if (transformer == null) {
return null;
}
}
return transform0(classLoader, classInternalName, classBeingRedefined, protectionDomain, classFileBuffer, transformer);
}
private byte[] transform0(ClassLoader classLoader, String classInternalName, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classFileBuffer, ClassFileTransformer transformer) {
final String className = JavaAssistUtils.jvmNameToJavaName(classInternalName);
if (isDebug) {
if (classBeingRedefined == null) {
logger.debug("[transform] classLoader:{} className:{} transformer:{}", classLoader, className, transformer.getClass().getName());
} else {
logger.debug("[retransform] classLoader:{} className:{} transformer:{}", classLoader, className, transformer.getClass().getName());
}
}
try {
final Thread thread = Thread.currentThread();
final ClassLoader before = getContextClassLoader(thread);
thread.setContextClassLoader(this.agentClassLoader);
try {
return transformer.transform(classLoader, className, classBeingRedefined, protectionDomain, classFileBuffer);
} finally {
// The context class loader have to be recovered even if it was null.
thread.setContextClassLoader(before);
}
} catch (Throwable e) {
logger.error("Transformer:{} threw an exception. cl:{} ctxCl:{} agentCl:{} Cause:{}",
transformer.getClass().getName(), classLoader, Thread.currentThread().getContextClassLoader(), agentClassLoader, e.getMessage(), e);
return null;
}
}
private ClassLoader getContextClassLoader(Thread thread) throws Throwable {
try {
return thread.getContextClassLoader();
} catch (SecurityException se) {
throw se;
} catch (Throwable th) {
if (isDebug) {
logger.debug("getContextClassLoader(). Caused:{}", th.getMessage(), th);
}
throw th;
}
}
private TransformerRegistry createTransformerRegistry(PluginContextLoadResult pluginContexts) {
DefaultTransformerRegistry registry = new DefaultTransformerRegistry();
for (ClassFileTransformer transformer : pluginContexts.getClassFileTransformer()) {
if (transformer instanceof MatchableClassFileTransformer) {
MatchableClassFileTransformer t = (MatchableClassFileTransformer) transformer;
logger.info("Registering class file transformer {} for {} ", t, t.getMatcher());
registry.addTransformer(t.getMatcher(), t);
} else {
logger.warn("Ignore class file transformer {}", transformer);
}
}
return registry;
}
}