2727import com .google .api .gax .core .GaxProperties ;
2828import com .google .api .gax .grpc .GrpcCallContext ;
2929import com .google .api .gax .grpc .GrpcInterceptorProvider ;
30- import com .google .api .gax .grpc .GrpcTransportChannel ;
3130import com .google .api .gax .longrunning .OperationTimedPollAlgorithm ;
3231import com .google .api .gax .retrying .RetrySettings ;
3332import com .google .api .gax .rpc .ApiCallContext ;
34- import com .google .api .gax .rpc .FixedTransportChannelProvider ;
3533import com .google .api .gax .rpc .TransportChannelProvider ;
3634import com .google .api .gax .tracing .ApiTracerFactory ;
3735import com .google .api .gax .tracing .BaseApiTracerFactory ;
7169import io .grpc .CompressorRegistry ;
7270import io .grpc .Context ;
7371import io .grpc .ExperimentalApi ;
74- import io .grpc .ManagedChannel ;
7572import io .grpc .ManagedChannelBuilder ;
7673import io .grpc .MethodDescriptor ;
7774import io .grpc .netty .shaded .io .grpc .netty .GrpcSslContexts ;
7875import io .grpc .netty .shaded .io .grpc .netty .NettyChannelBuilder ;
76+ import io .grpc .netty .shaded .io .netty .handler .ssl .SslContext ;
7977import io .opentelemetry .api .GlobalOpenTelemetry ;
8078import io .opentelemetry .api .OpenTelemetry ;
8179import io .opentelemetry .api .common .Attributes ;
8280import java .io .File ;
8381import java .io .IOException ;
8482import java .net .MalformedURLException ;
85- import java .net .URI ;
86- import java .net .URISyntaxException ;
8783import java .net .URL ;
8884import java .time .Duration ;
8985import java .util .ArrayList ;
9894import java .util .concurrent .ThreadFactory ;
9995import java .util .concurrent .TimeUnit ;
10096import java .util .concurrent .atomic .AtomicInteger ;
101- import java .util .logging .Level ;
102- import java .util .logging .Logger ;
10397import javax .annotation .Nonnull ;
10498import javax .annotation .Nullable ;
10599import javax .annotation .concurrent .GuardedBy ;
@@ -952,7 +946,6 @@ public static class Builder
952946 private CloseableExecutorProvider asyncExecutorProvider ;
953947 private String compressorName ;
954948 private String emulatorHost = System .getenv ("SPANNER_EMULATOR_HOST" );
955- private ManagedChannel managedChannel ;
956949 private boolean leaderAwareRoutingEnabled = true ;
957950 private boolean attemptDirectPath = true ;
958951 private DirectedReadOptions directedReadOptions ;
@@ -963,6 +956,7 @@ public static class Builder
963956 private boolean enableEndToEndTracing = SpannerOptions .environment .isEnableEndToEndTracing ();
964957 private boolean enableBuiltInMetrics = SpannerOptions .environment .isEnableBuiltInMetrics ();
965958 private String monitoringHost = SpannerOptions .environment .getMonitoringHost ();
959+ private SslContext mTLSContext = null ;
966960
967961 private static String createCustomClientLibToken (String token ) {
968962 return token + " " + ServiceOptions .getGoogApiClientLibName ();
@@ -1496,34 +1490,27 @@ public Builder setEmulatorHost(String emulatorHost) {
14961490 return this ;
14971491 }
14981492
1499- public Builder useClientCert (String host , String clientCertificate , String clientKey ) {
1493+ /**
1494+ * Configures mTLS authentication using the provided client certificate and key files. mTLS is
1495+ * only supported for external spanner hosts.
1496+ *
1497+ * @param clientCertificate Path to the client certificate file.
1498+ * @param clientCertificateKey Path to the client private key file.
1499+ * @throws SpannerException If an error occurs while configuring the mTLS context
1500+ */
1501+ @ ExperimentalApi ("https://github.com/googleapis/java-spanner/pull/3574" )
1502+ public Builder useClientCert (String clientCertificate , String clientCertificateKey ) {
15001503 try {
1501- URI uri = new URI (host );
1502- managedChannel =
1503- NettyChannelBuilder .forAddress (uri .getHost (), uri .getPort ())
1504- .sslContext (
1505- GrpcSslContexts .forClient ()
1506- .keyManager (new File (clientCertificate ), new File (clientKey ))
1507- .build ())
1504+ this .mTLSContext =
1505+ GrpcSslContexts .forClient ()
1506+ .keyManager (new File (clientCertificate ), new File (clientCertificateKey ))
15081507 .build ();
1509-
1510- setChannelProvider (
1511- FixedTransportChannelProvider .create (GrpcTransportChannel .create (managedChannel )));
1512- } catch (URISyntaxException e ) {
1513- throw new IllegalArgumentException (
1514- "Invalid host format. Expected format: 'protocol://host[:port]'." , e );
15151508 } catch (Exception e ) {
1516- throw new RuntimeException ( "Unexpected error during mTLS setup." , e );
1509+ throw SpannerExceptionFactory . asSpannerException ( e );
15171510 }
15181511 return this ;
15191512 }
15201513
1521- public Builder usePlainText () {
1522- this .setChannelConfigurator (ManagedChannelBuilder ::usePlaintext );
1523- this .setCredentials (NoCredentials .getInstance ());
1524- return this ;
1525- }
1526-
15271514 /**
15281515 * Sets OpenTelemetry object to be used for Spanner Metrics and Traces. GlobalOpenTelemetry will
15291516 * be used as fallback if this options is not set.
@@ -1632,24 +1619,15 @@ public SpannerOptions build() {
16321619 this .setChannelConfigurator (ManagedChannelBuilder ::usePlaintext );
16331620 // As we are using plain text, we should never send any credentials.
16341621 this .setCredentials (NoCredentials .getInstance ());
1635- } else if (managedChannel != null ) {
1636- // Add shutdown hook for the ManagedChannel if created to prevent resource leak
1637- Runtime .getRuntime ()
1638- .addShutdownHook (
1639- new Thread (
1640- () -> {
1641- final Logger logger = Logger .getLogger (SpannerOptions .class .getName ());
1642- try {
1643- managedChannel .shutdown ();
1644- logger .log (
1645- Level .INFO , "[SpannerOptions] ManagedChannel shut down successfully." );
1646- } catch (Exception e ) {
1647- logger .log (
1648- Level .WARNING ,
1649- "[SpannerOptions] Failed to shut down ManagedChannel." ,
1650- e );
1651- }
1652- }));
1622+ }
1623+ if (mTLSContext != null ) {
1624+ this .setChannelConfigurator (
1625+ builder -> {
1626+ if (builder instanceof NettyChannelBuilder ) {
1627+ ((NettyChannelBuilder ) builder ).sslContext (mTLSContext );
1628+ }
1629+ return builder ;
1630+ });
16531631 }
16541632 if (this .numChannels == null ) {
16551633 this .numChannels =
0 commit comments