Repository: pinpoint-apm/pinpoint
Issue: Add support for MySQL connector 6
Labels: enhancement
module:plugin

A lot of previous classes that were transformed have been deprecated, and new classes are created under `com.mysql.cj.jdbc.Driver` - https://dev.mysql.com/doc/connector-j/6.0/en/connector-j-api-changes.html


Issue comments:

Pull request review comments:

Patch that resolved the issue:
diff --git a/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/MySqlPlugin.java b/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/MySqlPlugin.java
index e3c7d74bc986..c40113ddd0c8 100644
--- a/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/MySqlPlugin.java
+++ b/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/MySqlPlugin.java
@@ -37,6 +37,7 @@
 
 /**
  * @author Jongho Moon
+ * @author HyunGil Jeong
  */
 public class MySqlPlugin implements ProfilerPlugin, TransformTemplateAware {
 
@@ -82,8 +83,22 @@ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, Strin
 
                 target.addField("com.navercorp.pinpoint.bootstrap.plugin.jdbc.DatabaseInfoAccessor");
 
-                InstrumentUtils.findConstructor(target, "java.lang.String", "int", "java.util.Properties", "java.lang.String", "java.lang.String")
-                        .addInterceptor("com.navercorp.pinpoint.plugin.jdbc.mysql.interceptor.MySQLConnectionCreateInterceptor");
+                InstrumentMethod constructor = target.getConstructor("java.lang.String", "int", "java.util.Properties", "java.lang.String", "java.lang.String");
+                if (constructor != null) {
+                    constructor.addInterceptor("com.navercorp.pinpoint.plugin.jdbc.mysql.interceptor.MySQLConnectionCreateInterceptor");
+                }
+                // 6.0.2 ~ 6.0.3
+                InstrumentMethod constructor_6_X = target.getConstructor("com.mysql.cj.core.ConnectionString", "java.lang.String", "int", "java.util.Properties");
+                if (constructor_6_X == null) {
+                    // 6.0.4+
+                    constructor_6_X = target.getConstructor("com.mysql.cj.core.conf.url.HostInfo");
+                }
+                if (constructor_6_X != null) {
+                    target.addGetter("com.navercorp.pinpoint.plugin.jdbc.mysql.interceptor.getter.OrigHostToConnectToGetter", "origHostToConnectTo");
+                    target.addGetter("com.navercorp.pinpoint.plugin.jdbc.mysql.interceptor.getter.OrigPortToConnectToGetter", "origPortToConnectTo");
+                    target.addGetter("com.navercorp.pinpoint.plugin.jdbc.mysql.interceptor.getter.DatabaseGetter", "database");
+                    constructor_6_X.addInterceptor("com.navercorp.pinpoint.plugin.jdbc.mysql.interceptor.MySQL_6_X_ConnectionCreateInterceptor");
+                }
 
                 // close
                 InstrumentUtils.findMethod(target, "close")
@@ -142,10 +157,12 @@ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, Strin
 
         transformTemplate.transform("com.mysql.jdbc.Connection", transformer);
         transformTemplate.transform("com.mysql.jdbc.ConnectionImpl", transformer);
+        // 6.x+
+        transformTemplate.transform("com.mysql.cj.jdbc.ConnectionImpl", transformer);
     }
 
     private void addDriverTransformer() {
-        transformTemplate.transform("com.mysql.jdbc.NonRegisteringDriver", new TransformCallback() {
+        TransformCallback transformCallback = new TransformCallback() {
 
             @Override
             public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
@@ -156,11 +173,14 @@ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, Strin
 
                 return target.toBytecode();
             }
-        });
+        };
+        transformTemplate.transform("com.mysql.jdbc.NonRegisteringDriver", transformCallback);
+        // 6.x+
+        transformTemplate.transform("com.mysql.cj.jdbc.NonRegisteringDriver", transformCallback);
     }
 
     private void addPreparedStatementTransformer(final MySqlConfig config) {
-        transformTemplate.transform("com.mysql.jdbc.PreparedStatement", new TransformCallback() {
+        TransformCallback transformCallback = new TransformCallback() {
 
             @Override
             public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
@@ -190,11 +210,14 @@ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, Strin
 
                 return target.toBytecode();
             }
-        });
+        };
+        transformTemplate.transform("com.mysql.jdbc.PreparedStatement", transformCallback);
+        // 6.x+
+        transformTemplate.transform("com.mysql.cj.jdbc.PreparedStatement", transformCallback);
     }
 
     private void addCallableStatementTransformer(final MySqlConfig config) {
-        transformTemplate.transform("com.mysql.jdbc.CallableStatement", new TransformCallback() {
+        TransformCallback transformCallback = new TransformCallback() {
 
             @Override
             public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws InstrumentException {
@@ -232,7 +255,10 @@ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, Strin
 
                 return target.toBytecode();
             }
-        });
+        };
+        transformTemplate.transform("com.mysql.jdbc.CallableStatement", transformCallback);
+        // 6.x+
+        transformTemplate.transform("com.mysql.cj.jdbc.CallableStatement", transformCallback);
     }
 
     private void addJDBC4PreparedStatementTransformer(final MySqlConfig config) {
@@ -308,6 +334,8 @@ public byte[] doInTransform(Instrumentor instrumentor, ClassLoader loader, Strin
 
         transformTemplate.transform("com.mysql.jdbc.Statement", transformer);
         transformTemplate.transform("com.mysql.jdbc.StatementImpl", transformer);
+        // 6.x+
+        transformTemplate.transform("com.mysql.cj.jdbc.StatementImpl", transformer);
 
     }
 
diff --git a/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/interceptor/MySQL_6_X_ConnectionCreateInterceptor.java b/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/interceptor/MySQL_6_X_ConnectionCreateInterceptor.java
new file mode 100644
index 000000000000..3ba860fcf68d
--- /dev/null
+++ b/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/interceptor/MySQL_6_X_ConnectionCreateInterceptor.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2017 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.jdbc.mysql.interceptor;
+
+import com.navercorp.pinpoint.bootstrap.context.DatabaseInfo;
+import com.navercorp.pinpoint.bootstrap.context.SpanEventRecorder;
+import com.navercorp.pinpoint.bootstrap.context.Trace;
+import com.navercorp.pinpoint.bootstrap.context.TraceContext;
+import com.navercorp.pinpoint.bootstrap.interceptor.AroundInterceptor;
+import com.navercorp.pinpoint.bootstrap.logging.PLogger;
+import com.navercorp.pinpoint.bootstrap.logging.PLoggerFactory;
+import com.navercorp.pinpoint.bootstrap.plugin.jdbc.DatabaseInfoAccessor;
+import com.navercorp.pinpoint.bootstrap.plugin.jdbc.DefaultDatabaseInfo;
+import com.navercorp.pinpoint.bootstrap.util.InterceptorUtils;
+import com.navercorp.pinpoint.plugin.jdbc.mysql.MySqlConstants;
+import com.navercorp.pinpoint.plugin.jdbc.mysql.interceptor.getter.DatabaseGetter;
+import com.navercorp.pinpoint.plugin.jdbc.mysql.interceptor.getter.OrigHostToConnectToGetter;
+import com.navercorp.pinpoint.plugin.jdbc.mysql.interceptor.getter.OrigPortToConnectToGetter;
+
+import java.util.Arrays;
+
+/**
+ * @author HyunGil Jeong
+ */
+// #1375 Workaround java level Deadlock
+// https://oss.navercorp.com/pinpoint/pinpoint-naver/issues/1375
+// @TargetConstructor({ "com.mysql.cj.core.ConnectionString", "java.lang.String", "int", "java.util.Properties" })
+public class MySQL_6_X_ConnectionCreateInterceptor implements AroundInterceptor {
+
+    private final PLogger logger = PLoggerFactory.getLogger(this.getClass());
+    private final boolean isDebug = logger.isDebugEnabled();
+
+    private final TraceContext traceContext;
+
+    public MySQL_6_X_ConnectionCreateInterceptor(TraceContext traceContext) {
+        this.traceContext = traceContext;
+    }
+
+    @Override
+    public void after(Object target, Object[] args, Object result, Throwable throwable) {
+        if (isDebug) {
+            logger.afterInterceptor(target, args, result, throwable);
+        }
+
+        final String hostToConnectTo = getHostToConnectTo(target);
+        final Integer portToConnectTo = getPortToConnectTo(target);
+        final String databaseId = getDatabase(target);
+        // In case of loadbalance, connectUrl is modified.
+        // final String url = getString(args[4]);
+        DatabaseInfo databaseInfo = null;
+        if (hostToConnectTo != null && portToConnectTo != null && databaseId != null) {
+            // It's dangerous to use this url directly
+            databaseInfo = createDatabaseInfo(hostToConnectTo, portToConnectTo, databaseId);
+            if (InterceptorUtils.isSuccess(throwable)) {
+                // Set only if connection is success.
+                if (target instanceof DatabaseInfoAccessor) {
+                    ((DatabaseInfoAccessor) target)._$PINPOINT$_setDatabaseInfo(databaseInfo);
+                }
+            }
+        }
+
+        final Trace trace = traceContext.currentTraceObject();
+        if (trace == null) {
+            return;
+        }
+
+        SpanEventRecorder recorder = trace.currentSpanEventRecorder();
+        // We must do this if current transaction is being recorded.
+        if (databaseInfo != null) {
+            recorder.recordServiceType(databaseInfo.getType());
+            recorder.recordEndPoint(databaseInfo.getMultipleHost());
+            recorder.recordDestinationId(databaseInfo.getDatabaseId());
+        }
+    }
+
+    private String getHostToConnectTo(Object target) {
+        if (target instanceof OrigHostToConnectToGetter) {
+            return ((OrigHostToConnectToGetter) target)._$PINPOINT$_getOrigHostToConnectTo();
+        }
+        return null;
+    }
+
+    private Integer getPortToConnectTo(Object target) {
+        if (target instanceof OrigPortToConnectToGetter) {
+            return ((OrigPortToConnectToGetter) target)._$PINPOINT$_getOrigPortToConnectTo();
+        }
+        return null;
+    }
+
+    private String getDatabase(Object target) {
+        if (target instanceof DatabaseGetter) {
+            return ((DatabaseGetter) target)._$PINPOINT$_getDatabase();
+        }
+        return null;
+    }
+    
+    private DatabaseInfo createDatabaseInfo(String url, Integer port, String databaseId) {
+        if (url.indexOf(':') == -1) {
+            url += ":" + port;
+        }
+
+        DatabaseInfo databaseInfo = new DefaultDatabaseInfo(MySqlConstants.MYSQL, MySqlConstants.MYSQL_EXECUTE_QUERY, url, url, Arrays.asList(url), databaseId);
+        return databaseInfo;
+
+    }
+
+    private String getString(Object value) {
+        if (value instanceof String) {
+            return (String) value;
+        }
+        return null;
+    }
+
+    private Integer getInteger(Object value) {
+        if (value instanceof Integer) {
+            return (Integer) value;
+        }
+        return null;
+    }
+
+    @Override
+    public void before(Object target, Object[] args) {
+
+    }
+}
diff --git a/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/interceptor/getter/DatabaseGetter.java b/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/interceptor/getter/DatabaseGetter.java
new file mode 100644
index 000000000000..436936376580
--- /dev/null
+++ b/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/interceptor/getter/DatabaseGetter.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2017 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.jdbc.mysql.interceptor.getter;
+
+/**
+ * @author HyunGil Jeong
+ */
+public interface DatabaseGetter {
+    String _$PINPOINT$_getDatabase();
+}
diff --git a/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/interceptor/getter/OrigHostToConnectToGetter.java b/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/interceptor/getter/OrigHostToConnectToGetter.java
new file mode 100644
index 000000000000..182dda8337a2
--- /dev/null
+++ b/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/interceptor/getter/OrigHostToConnectToGetter.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2017 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.jdbc.mysql.interceptor.getter;
+
+/**
+ * @author HyunGil Jeong
+ */
+public interface OrigHostToConnectToGetter {
+    String _$PINPOINT$_getOrigHostToConnectTo();
+}
diff --git a/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/interceptor/getter/OrigPortToConnectToGetter.java b/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/interceptor/getter/OrigPortToConnectToGetter.java
new file mode 100644
index 000000000000..dbb50c630caf
--- /dev/null
+++ b/plugins/mysql-jdbc/src/main/java/com/navercorp/pinpoint/plugin/jdbc/mysql/interceptor/getter/OrigPortToConnectToGetter.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2017 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.jdbc.mysql.interceptor.getter;
+
+/**
+ * @author HyunGil Jeong
+ */
+public interface OrigPortToConnectToGetter {
+    int _$PINPOINT$_getOrigPortToConnectTo();
+}


Test patch: