| 
26 | 26 | import com.google.cloud.spanner.connection.ConnectionPropertiesHelper;  | 
27 | 27 | import com.google.cloud.spanner.connection.ConnectionProperty;  | 
28 | 28 | import com.google.common.annotations.VisibleForTesting;  | 
 | 29 | +import com.google.common.base.Suppliers;  | 
29 | 30 | import com.google.rpc.Code;  | 
30 | 31 | import io.opentelemetry.api.OpenTelemetry;  | 
31 | 32 | import java.sql.Connection;  | 
@@ -222,7 +223,9 @@ public Connection connect(String url, Properties info) throws SQLException {  | 
222 | 223 |         Matcher matcherExternalHost = EXTERNAL_HOST_URL_PATTERN.matcher(url);  | 
223 | 224 |         if (matcher.matches() || matcherExternalHost.matches()) {  | 
224 | 225 |           // 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);  | 
226 | 229 |           String connectionUri = appendPropertiesToUrl(url.substring(5), info);  | 
227 | 230 |           ConnectionOptions options = buildConnectionOptions(connectionUri, info);  | 
228 | 231 |           JdbcConnection connection = new JdbcConnection(url, options);  | 
@@ -259,6 +262,48 @@ private ConnectionOptions buildConnectionOptions(String connectionUrl, Propertie  | 
259 | 262 |     return builder.build();  | 
260 | 263 |   }  | 
261 | 264 | 
 
  | 
 | 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 | + | 
262 | 307 |   private String appendPropertiesToUrl(String url, Properties info) {  | 
263 | 308 |     StringBuilder res = new StringBuilder(url);  | 
264 | 309 |     for (Entry<Object, Object> entry : info.entrySet()) {  | 
 | 
0 commit comments