1919import com .google .api .client .util .escape .CharEscapers ;
2020import com .google .cloud .bigquery .BigQueryOptions ;
2121import com .google .cloud .bigquery .exception .BigQueryJdbcRuntimeException ;
22+ import com .google .common .collect .ImmutableList ;
23+ import com .google .common .net .UrlEscapers ;
2224import java .io .UnsupportedEncodingException ;
2325import java .net .URLDecoder ;
24- import java .net .URLEncoder ;
2526import java .nio .charset .StandardCharsets ;
2627import java .util .Arrays ;
2728import java .util .Collections ;
2829import java .util .HashMap ;
2930import java .util .HashSet ;
31+ import java .util .LinkedHashMap ;
3032import java .util .List ;
3133import java .util .Map ;
3234import java .util .Map .Entry ;
4345 */
4446final class BigQueryJdbcUrlUtility {
4547
48+ private static final Map <String , Map <String , String >> PARSE_CACHE =
49+ Collections .synchronizedMap (
50+ new LinkedHashMap <String , Map <String , String >>(50 , 0.75f , true ) {
51+ protected boolean removeEldestEntry (Map .Entry <String , Map <String , String >> eldest ) {
52+ return size () > 50 ; // bound cache size
53+ }
54+ });
55+
4656 // TODO: Add all Connection options
4757 static final String ALLOW_LARGE_RESULTS_PROPERTY_NAME = "AllowLargeResults" ;
4858 static final String LARGE_RESULTS_TABLE_PROPERTY_NAME = "LargeResultTable" ;
@@ -127,7 +137,10 @@ final class BigQueryJdbcUrlUtility {
127137 static final String BYOID_TOKEN_URI_PROPERTY_NAME = "BYOID_TokenUri" ;
128138 static final String PARTNER_TOKEN_PROPERTY_NAME = "PartnerToken" ;
129139 private static final Pattern PARTNER_TOKEN_PATTERN =
130- Pattern .compile ("(?i)" + PARTNER_TOKEN_PROPERTY_NAME + "=\\ s*\\ (([^)]*)\\ )" );
140+ Pattern .compile (
141+ "(?i)(?:^|(?<=;))\\ s*"
142+ + PARTNER_TOKEN_PROPERTY_NAME
143+ + "\\ s*=\\ s*\\ (([^)]*)\\ )\\ s*(?=;|$)" );
131144 static final String METADATA_FETCH_THREAD_COUNT_PROPERTY_NAME = "MetaDataFetchThreadCount" ;
132145 static final int DEFAULT_METADATA_FETCH_THREAD_COUNT_VALUE = 32 ;
133146 static final String RETRY_TIMEOUT_IN_SECS_PROPERTY_NAME = "Timeout" ;
@@ -597,6 +610,12 @@ final class BigQueryJdbcUrlUtility {
597610 + " header." )
598611 .build ())));
599612
613+ private static final List <String > NETWORK_PROPERTIES =
614+ ImmutableList .of (
615+ PARTNER_TOKEN_PROPERTY_NAME ,
616+ ENDPOINT_OVERRIDES_PROPERTY_NAME ,
617+ PRIVATE_SERVICE_CONNECT_PROPERTY_NAME );
618+
600619 private static final Map <String , String > PROPERTY_NAME_MAP ;
601620
602621 static {
@@ -616,10 +635,9 @@ final class BigQueryJdbcUrlUtility {
616635 for (String p : BYOID_PROPERTIES ) {
617636 map .put (p .toUpperCase (), p );
618637 }
619- map .put (PARTNER_TOKEN_PROPERTY_NAME .toUpperCase (), PARTNER_TOKEN_PROPERTY_NAME );
620- map .put (ENDPOINT_OVERRIDES_PROPERTY_NAME .toUpperCase (), ENDPOINT_OVERRIDES_PROPERTY_NAME );
621- map .put (
622- PRIVATE_SERVICE_CONNECT_PROPERTY_NAME .toUpperCase (), PRIVATE_SERVICE_CONNECT_PROPERTY_NAME );
638+ for (String p : NETWORK_PROPERTIES ) {
639+ map .put (p .toUpperCase (), p );
640+ }
623641 PROPERTY_NAME_MAP = Collections .unmodifiableMap (map );
624642 }
625643
@@ -648,24 +666,33 @@ static String parseUriProperty(String uri, String property) {
648666 * @throws BigQueryJdbcRuntimeException if an unknown property is found or the URL is malformed.
649667 */
650668 static Map <String , String > parseUrl (String url ) {
669+ if (url == null ) {
670+ return Collections .emptyMap ();
671+ }
672+ return PARSE_CACHE .computeIfAbsent (url , BigQueryJdbcUrlUtility ::parseUrlInternal );
673+ }
674+
675+ private static Map <String , String > parseUrlInternal (String url ) {
651676 Map <String , String > map = new HashMap <>();
652677 if (url == null ) {
653678 return map ;
654679 }
655680
656- int start = url .indexOf ( ';' );
657- if (start == - 1 ) {
681+ String [] urlParts = url .split ( ";" , 2 );
682+ if (urlParts . length < 2 ) {
658683 return map ;
659684 }
660685
661- String urlToParse = url . substring ( start + 1 ) ;
686+ String urlToParse = urlParts [ 1 ] ;
662687
663688 // Parse PartnerToken separately as it contains ';'
664- StringBuilder urlBuilder = new StringBuilder (urlToParse );
665- String partnerToken = parseAndRemovePartnerTokenProperty (urlBuilder , "parseUrl" );
666- urlToParse = urlBuilder .toString ();
667- if (partnerToken != null ) {
668- map .put (PARTNER_TOKEN_PROPERTY_NAME , partnerToken );
689+ Matcher matcher = PARTNER_TOKEN_PATTERN .matcher (urlToParse );
690+ if (matcher .find ()) {
691+ String partnerToken = matcher .group (1 ).trim ();
692+ if (partnerToken .toUpperCase ().startsWith ("GPN:" )) {
693+ map .put (PARTNER_TOKEN_PROPERTY_NAME , " (" + partnerToken + ")" );
694+ urlToParse = matcher .replaceFirst ("" );
695+ }
669696 }
670697
671698 String [] parts = urlToParse .split (";" );
@@ -674,25 +701,17 @@ static Map<String, String> parseUrl(String url) {
674701 continue ;
675702 }
676703 String [] kv = part .split ("=" , 2 );
677- String key = kv [0 ].trim ();
678- if (kv .length == 1 ) {
679- if (!key .isEmpty ()) {
680- String safeKey = key .length () > 32 ? key .substring (0 , 32 ) + "..." : key ;
681- throw new BigQueryJdbcRuntimeException ("Property '" + safeKey + "' has no value." );
682- }
683- } else {
684- String value = kv [1 ]; // Value might be empty string if "Key="
685- if (!key .isEmpty ()) {
686- String upperCaseKey = key .toUpperCase ();
687- if (!PROPERTY_NAME_MAP .containsKey (upperCaseKey )) {
688- String safeKey = key .length () > 32 ? key .substring (0 , 32 ) + "..." : key ;
689- throw new BigQueryJdbcRuntimeException ("Unknown property: " + safeKey );
690- }
691- map .put (PROPERTY_NAME_MAP .get (upperCaseKey ), CharEscapers .decodeUriPath (value ));
692- }
704+ String key = kv [0 ].trim ().toUpperCase ();
705+ if (kv .length != 2 || !PROPERTY_NAME_MAP .containsKey (key )) {
706+ String ref = (kv .length == 2 ) ? key : part ;
707+ String safeRef = ref .length () > 32 ? ref .substring (0 , 32 ) + "..." : ref ;
708+ throw new BigQueryJdbcRuntimeException (
709+ String .format ("Wrong value or unknown setting: %s" , safeRef ));
693710 }
711+
712+ map .put (PROPERTY_NAME_MAP .get (key ), CharEscapers .decodeUriPath (kv [1 ]));
694713 }
695- return map ;
714+ return Collections . unmodifiableMap ( map ) ;
696715 }
697716
698717 /**
@@ -708,14 +727,11 @@ static String appendPropertiesToURL(String url, String callerClassName, Properti
708727 for (Entry <Object , Object > entry : properties .entrySet ()) {
709728 if (entry .getValue () != null && !"" .equals (entry .getValue ())) {
710729 LOG .finest ("Appending %s with value %s to URL" , entry .getKey (), entry .getValue ());
711- try {
712- String encodedValue =
713- URLEncoder .encode ((String ) entry .getValue (), StandardCharsets .UTF_8 .name ())
714- .replace ("+" , "%20" );
715- urlBuilder .append (";" ).append (entry .getKey ()).append ("=" ).append (encodedValue );
716- } catch (UnsupportedEncodingException e ) {
717- throw new BigQueryJdbcRuntimeException (e );
718- }
730+ String encodedValue =
731+ UrlEscapers .urlFormParameterEscaper ()
732+ .escape ((String ) entry .getValue ())
733+ .replace ("+" , "%20" );
734+ urlBuilder .append (";" ).append (entry .getKey ()).append ("=" ).append (encodedValue );
719735 }
720736 }
721737 return urlBuilder .toString ();
@@ -790,18 +806,11 @@ public static String parsePartnerTokenProperty(String url, String callerClassNam
790806 LOG .finest ("++enter++\t " + callerClassName );
791807 // This property is expected to be set by partners only. For more details on exact format
792808 // supported, refer b/396086960
793- return parseAndRemovePartnerTokenProperty (new StringBuilder (url ), callerClassName );
794- }
795-
796- private static String parseAndRemovePartnerTokenProperty (
797- StringBuilder urlBuilder , String callerClassName ) {
798- Matcher matcher = PARTNER_TOKEN_PATTERN .matcher (urlBuilder );
799-
809+ Matcher matcher = PARTNER_TOKEN_PATTERN .matcher (url );
800810 if (matcher .find ()) {
801- String content = matcher .group (1 ).trim ();
802- urlBuilder .delete (matcher .start (), matcher .end ());
803- if (content .toUpperCase ().startsWith ("GPN:" )) {
804- return " (" + content + ")" ;
811+ String partnerToken = matcher .group (1 ).trim ();
812+ if (partnerToken .toUpperCase ().startsWith ("GPN:" )) {
813+ return " (" + partnerToken + ")" ;
805814 }
806815 }
807816 return null ;
0 commit comments