/*
* 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.plugin.tomcat;
import java.security.ProtectionDomain;
import com.navercorp.pinpoint.bootstrap.async.AsyncTraceIdAccessor;
import com.navercorp.pinpoint.bootstrap.instrument.InstrumentClass;
import com.navercorp.pinpoint.bootstrap.instrument.InstrumentException;
import com.navercorp.pinpoint.bootstrap.instrument.InstrumentMethod;
import com.navercorp.pinpoint.bootstrap.instrument.MethodFilters;
import com.navercorp.pinpoint.bootstrap.instrument.Instrumentor;
import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformCallback;
import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformTemplate;
import com.navercorp.pinpoint.bootstrap.instrument.transformer.TransformTemplateAware;
import com.navercorp.pinpoint.bootstrap.logging.PLogger;
import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory;
import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin;
import com.navercorp.pinpoint.bootstrap.plugin.ProfilerPluginSetupContext;
import com.navercorp.pinpoint.bootstrap.resolver.ConditionProvider;
/**
* @author Jongho Moon
* @author jaehong.kim
*
*/
public class TomcatPlugin implements ProfilerPlugin, TransformTemplateAware {
private final PLogger logger = PLoggerFactory.getLogger(this.getClass());
private TransformTemplate transformTemplate;
/*
* (non-Javadoc)
*
* @see com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin#setUp(com.navercorp.pinpoint.bootstrap.plugin.ProfilerPluginSetupContext)
*/
@Override
public void setup(ProfilerPluginSetupContext context) {
final TomcatConfig config = new TomcatConfig(context.getConfig());
if (logger.isInfoEnabled()) {
logger.info("TomcatPlugin config:{}", config);
}
if (!config.isTomcatEnable()) {
logger.info("TomcatPlugin disabled");
return;
}
TomcatDetector tomcatDetector = new TomcatDetector(config.getTomcatBootstrapMains());
context.addApplicationTypeDetector(tomcatDetector);
if (shouldAddTransformers(config)) {
logger.info("Adding Tomcat transformers");
addTransformers(config);
} else {
logger.info("Not adding Tomcat transfomers");
}
}
private boolean shouldAddTransformers(TomcatConfig config) {
// Transform if conditional check is disabled
if (!config.isTomcatConditionalTransformEnable()) {
return true;
}
// Only transform if it's a Tomcat application or SpringBoot application
ConditionProvider conditionProvider = ConditionProvider.DEFAULT_CONDITION_PROVIDER;
boolean isTomcatApplication = conditionProvider.checkMainClass(config.getTomcatBootstrapMains());
boolean isSpringBootApplication = conditionProvider.checkMainClass(config.getSpringBootBootstrapMains());
return isTomcatApplication || isSpringBootApplication;
}
private void addTransformers(TomcatConfig config) {
if (config.isTomcatHidePinpointHeader()) {
addRequestFacadeEditor();
}
addRequestEditor();
addStandardHostValveEditor();
addStandardServiceEditor();
addTomcatConnectorEditor();
addWebappLoaderEditor();
addAsyncContextImpl();
}
private void addRequestEditor() {
transformTemplate.transform("org.apache.catalina.connector.Request", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
target.addField(TomcatConstants.TRACE_ACCESSOR);
target.addField(TomcatConstants.ASYNC_ACCESSOR);
// clear request.
InstrumentMethod recycleMethodEditorBuilder = target.getDeclaredMethod("recycle");
if (recycleMethodEditorBuilder != null) {
recycleMethodEditorBuilder.addInterceptor("com.navercorp.pinpoint.plugin.tomcat.interceptor.RequestRecycleInterceptor");
}
// trace asynchronous process.
InstrumentMethod startAsyncMethodEditor = target.getDeclaredMethod("startAsync", "javax.servlet.ServletRequest", "javax.servlet.ServletResponse");
if (startAsyncMethodEditor != null) {
startAsyncMethodEditor.addInterceptor("com.navercorp.pinpoint.plugin.tomcat.interceptor.RequestStartAsyncInterceptor");
}
return target.toBytecode();
}
});
}
private void addRequestFacadeEditor() {
transformTemplate.transform("org.apache.catalina.connector.RequestFacade", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
if (target != null) {
target.weave("com.navercorp.pinpoint.plugin.tomcat.aspect.RequestFacadeAspect");
return target.toBytecode();
}
return null;
}
});
}
private void addStandardHostValveEditor() {
transformTemplate.transform("org.apache.catalina.core.StandardHostValve", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
InstrumentMethod method = target.getDeclaredMethod("invoke", "org.apache.catalina.connector.Request", "org.apache.catalina.connector.Response");
if (method != null) {
method.addInterceptor("com.navercorp.pinpoint.plugin.tomcat.interceptor.StandardHostValveInvokeInterceptor");
}
return target.toBytecode();
}
});
}
private void addStandardServiceEditor() {
transformTemplate.transform("org.apache.catalina.core.StandardService", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
// Tomcat 6
InstrumentMethod startEditor = target.getDeclaredMethod("start");
if (startEditor != null) {
startEditor.addInterceptor("com.navercorp.pinpoint.plugin.tomcat.interceptor.StandardServiceStartInterceptor");
}
// Tomcat 7
InstrumentMethod startInternalEditor = target.getDeclaredMethod("startInternal");
if (startInternalEditor != null) {
startInternalEditor.addInterceptor("com.navercorp.pinpoint.plugin.tomcat.interceptor.StandardServiceStartInterceptor");
}
return target.toBytecode();
}
});
}
private void addTomcatConnectorEditor() {
transformTemplate.transform("org.apache.catalina.connector.Connector", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
// Tomcat 6
InstrumentMethod initializeEditor = target.getDeclaredMethod("initialize");
if (initializeEditor != null) {
initializeEditor.addInterceptor("com.navercorp.pinpoint.plugin.tomcat.interceptor.ConnectorInitializeInterceptor");
}
// Tomcat 7
InstrumentMethod initInternalEditor = target.getDeclaredMethod("initInternal");
if (initInternalEditor != null) {
initInternalEditor.addInterceptor("com.navercorp.pinpoint.plugin.tomcat.interceptor.ConnectorInitializeInterceptor");
}
return target.toBytecode();
}
});
}
private void addWebappLoaderEditor() {
transformTemplate.transform("org.apache.catalina.loader.WebappLoader", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
InstrumentMethod startMethod = null;
if (target.hasDeclaredMethod("start")) {
// Tomcat 6 - org.apache.catalina.loader.WebappLoader.start()
startMethod = target.getDeclaredMethod("start");
} else if (target.hasDeclaredMethod("startInternal")) {
// Tomcat 7, 8 - org.apache.catalina.loader.WebappLoader.startInternal()
startMethod = target.getDeclaredMethod("startInternal");
}
if (startMethod != null) {
startMethod.addInterceptor("com.navercorp.pinpoint.plugin.tomcat.interceptor.WebappLoaderStartInterceptor");
}
return target.toBytecode();
}
});
}
private void addAsyncContextImpl() {
transformTemplate.transform("org.apache.catalina.core.AsyncContextImpl", new TransformCallback() {
@Override
public byte[] doInTransform(Instrumentor instrumentor, ClassLoader classLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
InstrumentClass target = instrumentor.getInstrumentClass(classLoader, className, classfileBuffer);
target.addField(AsyncTraceIdAccessor.class.getName());
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("dispatch"))) {
method.addInterceptor("com.navercorp.pinpoint.plugin.tomcat.interceptor.AsyncContextImplDispatchMethodInterceptor");
}
return target.toBytecode();
}
});
}
@Override
public void setTransformTemplate(TransformTemplate transformTemplate) {
this.transformTemplate = transformTemplate;
}
}