88import java .io .IOException ;
99import java .time .Duration ;
1010import java .util .ArrayList ;
11+ import java .util .HashMap ;
1112import java .util .List ;
1213import java .util .Map ;
1314import java .util .Optional ;
14- import java .util .stream .Collectors ;
1515
1616import org .apache .commons .lang3 .StringUtils ;
1717import org .slf4j .Logger ;
1818import org .slf4j .LoggerFactory ;
19+ import org .springframework .http .HttpStatus ;
1920import org .springframework .lang .NonNull ;
2021import org .springframework .lang .Nullable ;
2122
2223import com .azure .core .credential .TokenCredential ;
24+ import com .azure .core .http .HttpHeader ;
2325import com .azure .core .http .policy .ExponentialBackoff ;
2426import com .azure .core .http .policy .RetryPolicy ;
27+ import com .azure .core .http .rest .PagedResponse ;
2528import com .azure .data .appconfiguration .ConfigurationAsyncClient ;
2629import com .azure .data .appconfiguration .ConfigurationClientBuilder ;
2730import com .azure .data .appconfiguration .models .ConfigurationSetting ;
@@ -45,21 +48,27 @@ public class ClientStore {
4548
4649 private ConfigurationClientBuilderSetup clientProvider ;
4750
51+ private HashMap <String , ConfigurationAsyncClient > clients ;
52+
4853 public ClientStore (AppConfigurationProviderProperties appProperties , ConnectionPool pool ,
49- AppConfigurationCredentialProvider tokenCredentialProvider ,
50- ConfigurationClientBuilderSetup clientProvider ) {
54+ AppConfigurationCredentialProvider tokenCredentialProvider ,
55+ ConfigurationClientBuilderSetup clientProvider ) {
5156 this .appProperties = appProperties ;
5257 this .pool = pool ;
5358 this .tokenCredentialProvider = tokenCredentialProvider ;
5459 this .clientProvider = clientProvider ;
60+ this .clients = new HashMap <String , ConfigurationAsyncClient >();
5561 }
5662
5763 private ConfigurationAsyncClient buildClient (String store ) throws IllegalArgumentException {
64+ if (clients .containsKey (store )) {
65+ return clients .get (store );
66+ }
5867 ConfigurationClientBuilder builder = getBuilder ();
5968 ExponentialBackoff retryPolicy = new ExponentialBackoff (appProperties .getMaxRetries (),
60- Duration .ofMillis (800 ), Duration .ofSeconds (8 ));
69+ Duration .ofMillis (800 ), Duration .ofSeconds (8 ));
6170 builder = builder .addPolicy (new BaseAppConfigurationPolicy ()).retryPolicy (new RetryPolicy (
62- retryPolicy ));
71+ retryPolicy ));
6372
6473 TokenCredential tokenCredential = null ;
6574 Connection connection = pool .get (store );
@@ -72,26 +81,26 @@ private ConfigurationAsyncClient buildClient(String store) throws IllegalArgumen
7281 tokenCredential = tokenCredentialProvider .getAppConfigCredential (endpoint );
7382 }
7483 if ((tokenCredential != null
75- || (connection .getClientId () != null && StringUtils .isNotEmpty (connection .getClientId ())))
76- && (connection != null && StringUtils .isNotEmpty (connection .getConnectionString ()))) {
84+ || (connection .getClientId () != null && StringUtils .isNotEmpty (connection .getClientId ())))
85+ && (connection != null && StringUtils .isNotEmpty (connection .getConnectionString ()))) {
7786 throw new IllegalArgumentException (
78- "More than 1 Conncetion method was set for connecting to App Configuration." );
87+ "More than 1 Conncetion method was set for connecting to App Configuration." );
7988 } else if (tokenCredential != null && connection != null && connection .getClientId () != null
80- && StringUtils .isNotEmpty (connection .getClientId ())) {
89+ && StringUtils .isNotEmpty (connection .getClientId ())) {
8190 throw new IllegalArgumentException (
82- "More than 1 Conncetion method was set for connecting to App Configuration." );
91+ "More than 1 Conncetion method was set for connecting to App Configuration." );
8392 }
8493
8594 if (tokenCredential != null ) {
8695 // User Provided Token Credential
8796 LOGGER .debug ("Connecting to " + endpoint + " using AppConfigurationCredentialProvider." );
8897 builder .credential (tokenCredential );
8998 } else if ((connection .getClientId () != null && StringUtils .isNotEmpty (connection .getClientId ()))
90- && connection .getEndpoint () != null ) {
99+ && connection .getEndpoint () != null ) {
91100 // User Assigned Identity - Client ID through configuration file.
92101 LOGGER .debug ("Connecting to " + endpoint + " using Client ID from configuration file." );
93102 ManagedIdentityCredentialBuilder micBuilder = new ManagedIdentityCredentialBuilder ()
94- .clientId (connection .getClientId ());
103+ .clientId (connection .getClientId ());
95104 builder .credential (micBuilder .build ());
96105 } else if (StringUtils .isNotEmpty (connection .getConnectionString ())) {
97106 // Connection String
@@ -101,7 +110,7 @@ private ConfigurationAsyncClient buildClient(String store) throws IllegalArgumen
101110 // System Assigned Identity. Needs to be checked last as all of the above
102111 // should have a Endpoint.
103112 LOGGER .debug ("Connecting to " + endpoint
104- + " using Azure System Assigned Identity or Azure User Assigned Identity." );
113+ + " using Azure System Assigned Identity or Azure User Assigned Identity." );
105114 ManagedIdentityCredentialBuilder micBuilder = new ManagedIdentityCredentialBuilder ();
106115 builder .credential (micBuilder .build ());
107116 } else {
@@ -114,47 +123,75 @@ private ConfigurationAsyncClient buildClient(String store) throws IllegalArgumen
114123 clientProvider .setup (builder , endpoint );
115124 }
116125
117- return builder .buildAsyncClient ();
126+ clients .put (store , builder .buildAsyncClient ());
127+ return clients .get (store );
118128 }
119129
120130 /**
121- * Gets the latest Configuration Setting from the revisions given config store that
122- * match the Setting Selector criteria.
131+ * Gets the latest Configuration Setting from the revisions given config store that match the Setting Selector
132+ * criteria.
123133 *
124- * @param settingSelector Information on which setting to pull. i.e. number of
125- * results, key value...
134+ * @param settingSelector Information on which setting to pull. i.e. number of results, key value...
126135 * @param storeName Name of the App Configuration store to query against.
127136 * @return List of Configuration Settings.
128137 */
129138 public final ConfigurationSetting getRevison (SettingSelector settingSelector , String storeName ) {
139+ PagedResponse <ConfigurationSetting > configurationRevision = null ;
140+ int retryCount = 0 ;
141+
130142 ConfigurationAsyncClient client = buildClient (storeName );
131- return client .listRevisions (settingSelector ).blockFirst ();
143+ while (retryCount <= appProperties .getMaxRetries ()) {
144+ configurationRevision = client .listRevisions (settingSelector ).byPage ().blockFirst ();
145+
146+ if (configurationRevision != null
147+ && configurationRevision .getStatusCode () == HttpStatus .TOO_MANY_REQUESTS .value ()) {
148+ HttpHeader retryAfterHeader = configurationRevision .getHeaders ().get ("retry-after-ms" );
149+
150+ if (retryAfterHeader != null ) {
151+ try {
152+ Integer retryAfter = Integer .valueOf (retryAfterHeader .getValue ());
153+
154+ Thread .sleep (retryAfter );
155+ } catch (NumberFormatException e ) {
156+ LOGGER .warn ("Unable to parse Retry After value." , e );
157+ } catch (InterruptedException e ) {
158+ LOGGER .warn ("Failed to wait after getting 429." , e );
159+ }
160+ }
161+
162+ configurationRevision = null ;
163+ } else if (configurationRevision != null && configurationRevision .getItems ().size () > 0 ) {
164+ return configurationRevision .getItems ().get (0 );
165+ } else {
166+ return null ;
167+ }
168+ retryCount ++;
169+ }
170+ return null ;
132171 }
133172
134173 /**
135- * Gets a list of Configuration Settings from the given config store that match the
136- * Setting Selector criteria.
174+ * Gets a list of Configuration Settings from the given config store that match the Setting Selector criteria.
137175 *
138- * @param settingSelector Information on which setting to pull. i.e. number of
139- * results, key value...
176+ * @param settingSelector Information on which setting to pull. i.e. number of results, key value...
140177 * @param storeName Name of the App Configuration store to query against.
141178 * @return List of Configuration Settings.
142179 * @throws IOException thrown when failed to retrieve values.
143180 */
144181 public final List <ConfigurationSetting > listSettings (SettingSelector settingSelector , String storeName )
145- throws IOException {
182+ throws IOException {
146183 ConfigurationAsyncClient client = buildClient (storeName );
147184
148185 return client .listConfigurationSettings (settingSelector ).collectList ().block ();
149186 }
150187
151188 /**
152- * Composite watched key names separated by comma, the key names is made up of:
153- * prefix, context and key name pattern e.g., prefix: /config, context: /application,
154- * watched key: my.watch.key will return: /config/application/my.watch.key
189+ * Composite watched key names separated by comma, the key names is made up of: prefix, context and key name pattern
190+ * e.g., prefix: /config, context: /application, watched key: my.watch.key will return:
191+ * /config/application/my.watch.key
155192 *
156- * The returned watched key will be one key pattern, one or multiple specific keys
157- * e.g., 1) * 2) /application/abc* 3) /application/abc 4) /application/abc,xyz
193+ * The returned watched key will be one key pattern, one or multiple specific keys e.g., 1) * 2) /application/abc*
194+ * 3) /application/abc 4) /application/abc,xyz
158195 *
159196 * @param store the {@code store} for which to composite watched key names
160197 * @param storeContextsMap map storing store name and List of context key-value pair
0 commit comments