17
17
package com .google .cloud .spanner .connection ;
18
18
19
19
import com .google .api .core .InternalApi ;
20
+ import com .google .api .gax .core .CredentialsProvider ;
20
21
import com .google .api .gax .rpc .TransportChannelProvider ;
21
22
import com .google .auth .Credentials ;
22
23
import com .google .auth .oauth2 .AccessToken ;
36
37
import com .google .common .base .Preconditions ;
37
38
import com .google .common .collect .Sets ;
38
39
import com .google .spanner .v1 .ExecuteSqlRequest .QueryOptions ;
40
+ import java .io .IOException ;
41
+ import java .lang .reflect .Constructor ;
42
+ import java .lang .reflect .InvocationTargetException ;
39
43
import java .net .URL ;
40
44
import java .util .ArrayList ;
41
45
import java .util .Arrays ;
42
46
import java .util .Collections ;
43
47
import java .util .HashSet ;
44
48
import java .util .List ;
49
+ import java .util .Objects ;
45
50
import java .util .Set ;
46
51
import java .util .regex .Matcher ;
47
52
import java .util .regex .Pattern ;
53
+ import java .util .stream .Stream ;
48
54
import javax .annotation .Nullable ;
49
55
50
56
/**
@@ -182,6 +188,8 @@ public String[] getValidValues() {
182
188
public static final String CREDENTIALS_PROPERTY_NAME = "credentials" ;
183
189
/** Name of the 'encodedCredentials' connection property. */
184
190
public static final String ENCODED_CREDENTIALS_PROPERTY_NAME = "encodedCredentials" ;
191
+ /** Name of the 'credentialsProvider' connection property. */
192
+ public static final String CREDENTIALS_PROVIDER_PROPERTY_NAME = "credentialsProvider" ;
185
193
/**
186
194
* OAuth token to use for authentication. Cannot be used in combination with a credentials file.
187
195
*/
@@ -231,6 +239,9 @@ public String[] getValidValues() {
231
239
ConnectionProperty .createStringProperty (
232
240
ENCODED_CREDENTIALS_PROPERTY_NAME ,
233
241
"Base64-encoded credentials to use for this connection. If neither this property or a credentials location are set, the connection will use the default Google Cloud credentials for the runtime environment." ),
242
+ ConnectionProperty .createStringProperty (
243
+ CREDENTIALS_PROVIDER_PROPERTY_NAME ,
244
+ "The class name of the com.google.api.gax.core.CredentialsProvider implementation that should be used to obtain credentials for connections." ),
234
245
ConnectionProperty .createStringProperty (
235
246
OAUTH_TOKEN_PROPERTY_NAME ,
236
247
"A valid pre-existing OAuth token to use for authentication for this connection. Setting this property will take precedence over any value set for a credentials file." ),
@@ -386,6 +397,12 @@ private boolean isValidUri(String uri) {
386
397
* <li>encodedCredentials (String): A Base64 encoded string containing the Google credentials
387
398
* to use. You should only set either this property or the `credentials` (file location)
388
399
* property, but not both at the same time.
400
+ * <li>credentialsProvider (String): Class name of the {@link
401
+ * com.google.api.gax.core.CredentialsProvider} that should be used to get credentials for
402
+ * a connection that is created by this {@link ConnectionOptions}. The credentials will be
403
+ * retrieved from the {@link com.google.api.gax.core.CredentialsProvider} when a new
404
+ * connection is created. A connection will use the credentials that were obtained at
405
+ * creation during its lifetime.
389
406
* <li>autocommit (boolean): Sets the initial autocommit mode for the connection. Default is
390
407
* true.
391
408
* <li>readonly (boolean): Sets the initial readonly mode for the connection. Default is
@@ -501,6 +518,7 @@ public static Builder newBuilder() {
501
518
private final String warnings ;
502
519
private final String credentialsUrl ;
503
520
private final String encodedCredentials ;
521
+ private final CredentialsProvider credentialsProvider ;
504
522
private final String oauthToken ;
505
523
private final Credentials fixedCredentials ;
506
524
@@ -537,22 +555,22 @@ private ConnectionOptions(Builder builder) {
537
555
this .credentialsUrl =
538
556
builder .credentialsUrl != null ? builder .credentialsUrl : parseCredentials (builder .uri );
539
557
this .encodedCredentials = parseEncodedCredentials (builder .uri );
540
- // Check that not both a credentials location and encoded credentials have been specified in the
541
- // connection URI.
542
- Preconditions .checkArgument (
543
- this .credentialsUrl == null || this .encodedCredentials == null ,
544
- "Cannot specify both a credentials URL and encoded credentials. Only set one of the properties." );
545
-
558
+ this .credentialsProvider = parseCredentialsProvider (builder .uri );
546
559
this .oauthToken =
547
560
builder .oauthToken != null ? builder .oauthToken : parseOAuthToken (builder .uri );
548
- this . fixedCredentials = builder . credentials ;
549
- // Check that not both credentials and an OAuth token have been specified .
561
+ // Check that at most one of credentials location, encoded credentials, credentials provider and
562
+ // OUAuth token has been specified in the connection URI .
550
563
Preconditions .checkArgument (
551
- (builder .credentials == null
552
- && this .credentialsUrl == null
553
- && this .encodedCredentials == null )
554
- || this .oauthToken == null ,
555
- "Cannot specify both credentials and an OAuth token." );
564
+ Stream .of (
565
+ this .credentialsUrl ,
566
+ this .encodedCredentials ,
567
+ this .credentialsProvider ,
568
+ this .oauthToken )
569
+ .filter (Objects ::nonNull )
570
+ .count ()
571
+ <= 1 ,
572
+ "Specify only one of credentialsUrl, encodedCredentials, credentialsProvider and OAuth token" );
573
+ this .fixedCredentials = builder .credentials ;
556
574
557
575
this .userAgent = parseUserAgent (this .uri );
558
576
QueryOptions .Builder queryOptionsBuilder = QueryOptions .newBuilder ();
@@ -570,14 +588,24 @@ private ConnectionOptions(Builder builder) {
570
588
// Using credentials on a plain text connection is not allowed, so if the user has not specified
571
589
// any credentials and is using a plain text connection, we should not try to get the
572
590
// credentials from the environment, but default to NoCredentials.
573
- if (builder . credentials == null
591
+ if (this . fixedCredentials == null
574
592
&& this .credentialsUrl == null
575
593
&& this .encodedCredentials == null
594
+ && this .credentialsProvider == null
576
595
&& this .oauthToken == null
577
596
&& this .usePlainText ) {
578
597
this .credentials = NoCredentials .getInstance ();
579
598
} else if (this .oauthToken != null ) {
580
599
this .credentials = new GoogleCredentials (new AccessToken (oauthToken , null ));
600
+ } else if (this .credentialsProvider != null ) {
601
+ try {
602
+ this .credentials = this .credentialsProvider .getCredentials ();
603
+ } catch (IOException exception ) {
604
+ throw SpannerExceptionFactory .newSpannerException (
605
+ ErrorCode .INVALID_ARGUMENT ,
606
+ "Failed to get credentials from CredentialsProvider: " + exception .getMessage (),
607
+ exception );
608
+ }
581
609
} else if (this .fixedCredentials != null ) {
582
610
this .credentials = fixedCredentials ;
583
611
} else if (this .encodedCredentials != null ) {
@@ -691,18 +719,49 @@ static boolean parseRetryAbortsInternally(String uri) {
691
719
}
692
720
693
721
@ VisibleForTesting
694
- static String parseCredentials (String uri ) {
722
+ static @ Nullable String parseCredentials (String uri ) {
695
723
String value = parseUriProperty (uri , CREDENTIALS_PROPERTY_NAME );
696
724
return value != null ? value : DEFAULT_CREDENTIALS ;
697
725
}
698
726
699
727
@ VisibleForTesting
700
- static String parseEncodedCredentials (String uri ) {
728
+ static @ Nullable String parseEncodedCredentials (String uri ) {
701
729
return parseUriProperty (uri , ENCODED_CREDENTIALS_PROPERTY_NAME );
702
730
}
703
731
704
732
@ VisibleForTesting
705
- static String parseOAuthToken (String uri ) {
733
+ static @ Nullable CredentialsProvider parseCredentialsProvider (String uri ) {
734
+ String name = parseUriProperty (uri , CREDENTIALS_PROVIDER_PROPERTY_NAME );
735
+ if (name != null ) {
736
+ try {
737
+ Class <? extends CredentialsProvider > clazz =
738
+ (Class <? extends CredentialsProvider >) Class .forName (name );
739
+ Constructor <? extends CredentialsProvider > constructor = clazz .getDeclaredConstructor ();
740
+ return constructor .newInstance ();
741
+ } catch (ClassNotFoundException classNotFoundException ) {
742
+ throw SpannerExceptionFactory .newSpannerException (
743
+ ErrorCode .INVALID_ARGUMENT ,
744
+ "Unknown or invalid CredentialsProvider class name: " + name ,
745
+ classNotFoundException );
746
+ } catch (NoSuchMethodException noSuchMethodException ) {
747
+ throw SpannerExceptionFactory .newSpannerException (
748
+ ErrorCode .INVALID_ARGUMENT ,
749
+ "Credentials provider " + name + " does not have a public no-arg constructor." ,
750
+ noSuchMethodException );
751
+ } catch (InvocationTargetException
752
+ | InstantiationException
753
+ | IllegalAccessException exception ) {
754
+ throw SpannerExceptionFactory .newSpannerException (
755
+ ErrorCode .INVALID_ARGUMENT ,
756
+ "Failed to create an instance of " + name + ": " + exception .getMessage (),
757
+ exception );
758
+ }
759
+ }
760
+ return null ;
761
+ }
762
+
763
+ @ VisibleForTesting
764
+ static @ Nullable String parseOAuthToken (String uri ) {
706
765
String value = parseUriProperty (uri , OAUTH_TOKEN_PROPERTY_NAME );
707
766
return value != null ? value : DEFAULT_OAUTH_TOKEN ;
708
767
}
@@ -849,6 +908,10 @@ Credentials getFixedCredentials() {
849
908
return this .fixedCredentials ;
850
909
}
851
910
911
+ CredentialsProvider getCredentialsProvider () {
912
+ return this .credentialsProvider ;
913
+ }
914
+
852
915
/** The {@link SessionPoolOptions} of this {@link ConnectionOptions}. */
853
916
public SessionPoolOptions getSessionPoolOptions () {
854
917
return sessionPoolOptions ;
0 commit comments