Skip to content

Commit 80e5c0c

Browse files
authored
chore: set Hibernate user-agent automatically (#1975)
Use the call stack the first time that a JDBC connection is being created to determine whether the connection should use the sp-jdbc or sp-hib user-agent token.
1 parent 6b7ddf4 commit 80e5c0c

File tree

1 file changed

+46
-1
lines changed

1 file changed

+46
-1
lines changed

src/main/java/com/google/cloud/spanner/jdbc/JdbcDriver.java

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import com.google.cloud.spanner.connection.ConnectionPropertiesHelper;
2727
import com.google.cloud.spanner.connection.ConnectionProperty;
2828
import com.google.common.annotations.VisibleForTesting;
29+
import com.google.common.base.Suppliers;
2930
import com.google.rpc.Code;
3031
import io.opentelemetry.api.OpenTelemetry;
3132
import java.sql.Connection;
@@ -222,7 +223,9 @@ public Connection connect(String url, Properties info) throws SQLException {
222223
Matcher matcherExternalHost = EXTERNAL_HOST_URL_PATTERN.matcher(url);
223224
if (matcher.matches() || matcherExternalHost.matches()) {
224225
// strip 'jdbc:' from the URL, add any extra properties and pass on to the generic
225-
// Connection API
226+
// Connection API. Also set the user-agent if we detect that the connection
227+
// comes from known framework like Hibernate, and there is no other user-agent set.
228+
maybeAddUserAgent(info);
226229
String connectionUri = appendPropertiesToUrl(url.substring(5), info);
227230
ConnectionOptions options = buildConnectionOptions(connectionUri, info);
228231
JdbcConnection connection = new JdbcConnection(url, options);
@@ -259,6 +262,48 @@ private ConnectionOptions buildConnectionOptions(String connectionUrl, Propertie
259262
return builder.build();
260263
}
261264

265+
static void maybeAddUserAgent(Properties properties) {
266+
if (properties.containsKey("userAgent")) {
267+
return;
268+
}
269+
if (isHibernate()) {
270+
properties.setProperty("userAgent", "sp-hib");
271+
}
272+
}
273+
274+
static boolean isHibernate() {
275+
// Cache the result as the check is relatively expensive, and we also don't want to create
276+
// multiple different Spanner instances just to get the correct user-agent in every case.
277+
return Suppliers.memoize(
278+
() -> {
279+
try {
280+
// First check if the Spanner Hibernate dialect is on the classpath. If it is, then
281+
// we assume that Hibernate will (eventually) be used.
282+
Class.forName(
283+
"com.google.cloud.spanner.hibernate.SpannerDialect",
284+
/*initialize=*/ false,
285+
JdbcDriver.class.getClassLoader());
286+
return true;
287+
} catch (Throwable ignore) {
288+
}
289+
290+
// If we did not find the Spanner Hibernate dialect on the classpath, then do a
291+
// check if the connection is still being created by Hibernate using the built-in
292+
// Spanner dialect in Hibernate.
293+
try {
294+
StackTraceElement[] callStack = Thread.currentThread().getStackTrace();
295+
for (StackTraceElement element : callStack) {
296+
if (element.getClassName().contains(".hibernate.")) {
297+
return true;
298+
}
299+
}
300+
} catch (Throwable ignore) {
301+
}
302+
return false;
303+
})
304+
.get();
305+
}
306+
262307
private String appendPropertiesToUrl(String url, Properties info) {
263308
StringBuilder res = new StringBuilder(url);
264309
for (Entry<Object, Object> entry : info.entrySet()) {

0 commit comments

Comments
 (0)