/*
* 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.jackson;
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.Instrumentor;
import com.navercorp.pinpoint.bootstrap.instrument.MethodFilters;
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 static com.navercorp.pinpoint.common.util.VarArgs.va;
import java.security.ProtectionDomain;
/**
* @author Sungkook Kim
*
*/
public class JacksonPlugin implements ProfilerPlugin, TransformTemplateAware {
private static final String JACKSON_SCOPE = "JACKSON_OBJECTMAPPER_SCOPE";
private static final String BASIC_METHOD_INTERCEPTOR = "com.navercorp.pinpoint.bootstrap.interceptor.BasicMethodInterceptor";
private static final String READ_VALUE_INTERCEPTOR = "com.navercorp.pinpoint.plugin.jackson.interceptor.ReadValueInterceptor";
private static final String WRITE_VALUE_AS_BYTES_INTERCEPTOR = "com.navercorp.pinpoint.plugin.jackson.interceptor.WriteValueAsBytesInterceptor";
private static final String WRITE_VALUE_AS_STRING_INTERCEPTOR = "com.navercorp.pinpoint.plugin.jackson.interceptor.WriteValueAsStringInterceptor";
private final PLogger logger = PLoggerFactory.getLogger(this.getClass());
private TransformTemplate transformTemplate;
@Override
public void setup(ProfilerPluginSetupContext context) {
JacksonConfig config = new JacksonConfig(context.getConfig());
logger.debug("[Jackson] Initialized config={}", config);
if (config.isProfile()) {
addObjectMapperEditor("com.fasterxml.jackson.databind.ObjectMapper");
addObjectReaderEditor("com.fasterxml.jackson.databind.ObjectReader");
addObjectWriterEditor("com.fasterxml.jackson.databind.ObjectWriter");
addObjectMapper_1_X_Editor("org.codehaus.jackson.map.ObjectMapper");
addObjectReaderEditor("org.codehaus.jackson.map.ObjectReader");
addObjectWriterEditor("org.codehaus.jackson.map.ObjectWriter");
}
}
private void addObjectMapperEditor(String clazzName) {
transformTemplate.transform(clazzName, 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);
final InstrumentMethod constructor1 = target.getConstructor();
addInterceptor(constructor1, BASIC_METHOD_INTERCEPTOR, va(JacksonConstants.SERVICE_TYPE));
final InstrumentMethod constructor2 = target.getConstructor("com.fasterxml.jackson.core.JsonFactory");
addInterceptor(constructor2, BASIC_METHOD_INTERCEPTOR, va(JacksonConstants.SERVICE_TYPE));
final InstrumentMethod constructor3 = target.getConstructor("com.fasterxml.jackson.core.JsonFactory", "com.fasterxml.jackson.databind.ser.DefaultSerializerProvider", "com.fasterxml.jackson.databind.deser.DefaultDeserializationContext");
addInterceptor(constructor3, BASIC_METHOD_INTERCEPTOR, va(JacksonConstants.SERVICE_TYPE));
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("writeValue"))) {
addInterceptor(method, BASIC_METHOD_INTERCEPTOR, va(JacksonConstants.SERVICE_TYPE));
}
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("writeValueAsString"))) {
addInterceptor(method, WRITE_VALUE_AS_STRING_INTERCEPTOR);
}
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("writeValueAsBytes"))) {
addInterceptor(method, WRITE_VALUE_AS_BYTES_INTERCEPTOR);
}
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("readValue"))) {
addInterceptor(method, READ_VALUE_INTERCEPTOR);
}
return target.toBytecode();
}
});
}
private void addObjectMapper_1_X_Editor(String clazzName) {
transformTemplate.transform(clazzName, 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);
final InstrumentMethod constructor1 = target.getConstructor();
addInterceptor(constructor1, BASIC_METHOD_INTERCEPTOR, va(JacksonConstants.SERVICE_TYPE));
final InstrumentMethod constructor2 = target.getConstructor("org.codehaus.jackson.JsonFactory");
addInterceptor(constructor2, BASIC_METHOD_INTERCEPTOR, va(JacksonConstants.SERVICE_TYPE));
final InstrumentMethod constructor3 = target.getConstructor("org.codehaus.jackson.JsonFactory", "org.codehaus.jackson.map.SerializerProvider", "org.codehaus.jackson.map.DeserializerProvider");
addInterceptor(constructor3, BASIC_METHOD_INTERCEPTOR, va(JacksonConstants.SERVICE_TYPE));
final InstrumentMethod constructor4 = target.getConstructor("org.codehaus.jackson.map.SerializerFactory");
addInterceptor(constructor4, BASIC_METHOD_INTERCEPTOR, va(JacksonConstants.SERVICE_TYPE));
final InstrumentMethod constructor5 = target.getConstructor("org.codehaus.jackson.JsonFactory", "org.codehaus.jackson.map.SerializerProvider", "org.codehaus.jackson.map.DeserializerProvider", "org.codehaus.jackson.map.SerializationConfig", "org.codehaus.jackson.map.DeserializationConfig");
addInterceptor(constructor5, BASIC_METHOD_INTERCEPTOR, va(JacksonConstants.SERVICE_TYPE));
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("writeValue"))) {
addInterceptor(method, BASIC_METHOD_INTERCEPTOR, va(JacksonConstants.SERVICE_TYPE));
}
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("writeValueAsString"))) {
addInterceptor(method, WRITE_VALUE_AS_STRING_INTERCEPTOR);
}
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("writeValueAsBytes"))) {
addInterceptor(method, WRITE_VALUE_AS_BYTES_INTERCEPTOR);
}
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("readValue"))) {
addInterceptor(method, READ_VALUE_INTERCEPTOR);
}
return target.toBytecode();
}
});
}
private void addObjectReaderEditor(String clazzName) {
transformTemplate.transform(clazzName, 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);
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("readValue", "readValues"))) {
addInterceptor(method, READ_VALUE_INTERCEPTOR);
}
return target.toBytecode();
}
});
}
private void addObjectWriterEditor(String clazzName) {
transformTemplate.transform(clazzName, 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);
// InterceptorScope scope = instrumentor.getInterceptorScope(JACKSON_SCOPE);
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("writeValue"))) {
addInterceptor(method, BASIC_METHOD_INTERCEPTOR, va(JacksonConstants.SERVICE_TYPE));
}
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("writeValueAsString"))) {
addInterceptor(method, WRITE_VALUE_AS_STRING_INTERCEPTOR);
}
for (InstrumentMethod method : target.getDeclaredMethods(MethodFilters.name("writeValueAsBytes"))) {
addInterceptor(method, WRITE_VALUE_AS_BYTES_INTERCEPTOR);
}
return target.toBytecode();
}
});
}
private boolean addInterceptor(InstrumentMethod method, String interceptorClassName) {
if (method != null) {
try {
method.addScopedInterceptor(interceptorClassName, JACKSON_SCOPE);
return true;
} catch (InstrumentException e) {
if (logger.isWarnEnabled()) {
logger.warn("Unsupported method " + method, e);
}
}
}
return false;
}
private boolean addInterceptor(InstrumentMethod method, String interceptorClassName, Object[] constructorArgs) {
if (method != null) {
try {
method.addScopedInterceptor(interceptorClassName, constructorArgs, JACKSON_SCOPE);
return true;
} catch (InstrumentException e) {
if (logger.isWarnEnabled()) {
logger.warn("Unsupported method " + method, e);
}
}
}
return false;
}
@Override
public void setTransformTemplate(TransformTemplate transformTemplate) {
this.transformTemplate = transformTemplate;
}
}