@@ -6,7 +6,7 @@ package software.aws.toolkits.jetbrains.core
6
6
import com.intellij.execution.configurations.GeneralCommandLine
7
7
import com.intellij.openapi.Disposable
8
8
import com.intellij.openapi.application.ApplicationManager
9
- import com.intellij.openapi.components.ServiceManager
9
+ import com.intellij.openapi.components.service
10
10
import com.intellij.openapi.project.Project
11
11
import com.intellij.util.Alarm
12
12
import com.intellij.util.AlarmFactory
@@ -18,6 +18,7 @@ import software.aws.toolkits.core.region.AwsRegion
18
18
import software.aws.toolkits.core.utils.getLogger
19
19
import software.aws.toolkits.core.utils.warn
20
20
import software.aws.toolkits.jetbrains.core.credentials.AwsConnectionManager
21
+ import software.aws.toolkits.jetbrains.core.credentials.ConnectionSettings
21
22
import software.aws.toolkits.jetbrains.core.credentials.CredentialManager
22
23
import software.aws.toolkits.jetbrains.core.credentials.toEnvironmentVariables
23
24
import software.aws.toolkits.jetbrains.core.executables.ExecutableInstance
@@ -34,28 +35,22 @@ import java.util.concurrent.ExecutionException
34
35
import java.util.concurrent.TimeUnit
35
36
import kotlin.reflect.KClass
36
37
38
+ // Getting resources can take a long time on a slow connection or if there are a lot of resources. This call should
39
+ // always be done in an async context so it should be OK to take multiple seconds.
40
+ private val DEFAULT_TIMEOUT = Duration .ofSeconds(30 )
41
+
37
42
/* *
38
43
* Intended to prevent repeated unnecessary calls to AWS to understand resource state.
39
44
*
40
45
* Will cache responses from AWS by [AwsRegion]/[ToolkitCredentialsProvider] - generically applicable to any AWS call.
41
46
*/
42
47
interface AwsResourceCache {
43
-
44
48
/* *
45
- * Get a [resource] either by making a call or returning it from the cache if present and unexpired. Uses the currently [AwsRegion]
46
- * & [ToolkitCredentialsProvider] active in [AwsConnectionManager].
49
+ * Get a [resource] either by making a call or returning it from the cache if present and unexpired.
47
50
*
48
51
* @param[useStale] if an exception occurs attempting to refresh the resource return a cached version if it exists (even if it's expired). Default: true
49
52
* @param[forceFetch] force the resource to refresh (and update cache) even if a valid cache version exists. Default: false
50
53
*/
51
- fun <T > getResource (resource : Resource <T >, useStale : Boolean = true, forceFetch : Boolean = false): CompletionStage <T >
52
-
53
- /* *
54
- * @see [getResource]
55
- *
56
- * @param[region] the specific [AwsRegion] to use for this resource
57
- * @param[credentialProvider] the specific [ToolkitCredentialsProvider] to use for this resource
58
- */
59
54
fun <T > getResource (
60
55
resource : Resource <T >,
61
56
region : AwsRegion ,
@@ -65,13 +60,14 @@ interface AwsResourceCache {
65
60
): CompletionStage <T >
66
61
67
62
/* *
68
- * Blocking version of [getResource]
69
- *
70
- * @param[useStale] if an exception occurs attempting to refresh the resource return a cached version if it exists (even if it's expired). Default: true
71
- * @param[forceFetch] force the resource to refresh (and update cache) even if a valid cache version exists. Default: false
63
+ * @see [getResource]
72
64
*/
73
- fun <T > getResourceNow (resource : Resource <T >, timeout : Duration = DEFAULT_TIMEOUT , useStale : Boolean = true, forceFetch : Boolean = false): T =
74
- wait(timeout) { getResource(resource, useStale, forceFetch) }
65
+ fun <T > getResource (
66
+ resource : Resource <T >,
67
+ connectionSettings : ConnectionSettings ,
68
+ useStale : Boolean = true,
69
+ forceFetch : Boolean = false
70
+ ): CompletionStage <T > = getResource(resource, connectionSettings.region, connectionSettings.credentials, useStale, forceFetch)
75
71
76
72
/* *
77
73
* Blocking version of [getResource]
@@ -89,11 +85,15 @@ interface AwsResourceCache {
89
85
): T = wait(timeout) { getResource(resource, region, credentialProvider, useStale, forceFetch) }
90
86
91
87
/* *
92
- * Gets the [resource] if it exists in the cache.
93
- *
94
- * @param[useStale] return a cached version if it exists (even if it's expired). Default: true
88
+ * Blocking version of [getResource]
95
89
*/
96
- fun <T > getResourceIfPresent (resource : Resource <T >, useStale : Boolean = true): T ?
90
+ fun <T > getResourceNow (
91
+ resource : Resource <T >,
92
+ connectionSettings : ConnectionSettings ,
93
+ timeout : Duration = DEFAULT_TIMEOUT ,
94
+ useStale : Boolean = true,
95
+ forceFetch : Boolean = false
96
+ ): T = getResourceNow(resource, connectionSettings.region, connectionSettings.credentials, timeout, useStale, forceFetch)
97
97
98
98
/* *
99
99
* Gets the [resource] if it exists in the cache.
@@ -103,28 +103,30 @@ interface AwsResourceCache {
103
103
*/
104
104
fun <T > getResourceIfPresent (resource : Resource <T >, region : AwsRegion , credentialProvider : ToolkitCredentialsProvider , useStale : Boolean = true): T ?
105
105
106
+ /* *
107
+ * Gets the [resource] if it exists in the cache.
108
+ */
109
+ fun <T > getResourceIfPresent (resource : Resource <T >, connectionSettings : ConnectionSettings , useStale : Boolean = true): T ? =
110
+ getResourceIfPresent(resource, connectionSettings.region, connectionSettings.credentials, useStale)
111
+
106
112
/* *
107
113
* Clears the contents of the cache across all regions, credentials and resource types.
108
114
*/
109
115
fun clear ()
110
116
111
117
/* *
112
- * Clears the contents of the cache for the specific [resource] type, in the currently active [AwsRegion] & [ToolkitCredentialsProvider ]
118
+ * Clears the contents of the cache for the specific [ConnectionSettings ]
113
119
*/
114
- fun clear (resource : Resource < * > )
120
+ fun clear (connectionSettings : ConnectionSettings )
115
121
116
122
/* *
117
- * Clears the contents of the cache for the specific [resource] type, [AwsRegion ] & [ToolkitCredentialsProvider ]
123
+ * Clears the contents of the cache for the specific [resource] type] & [ConnectionSettings ]
118
124
*/
119
- fun clear (resource : Resource <* >, region : AwsRegion , credentialProvider : ToolkitCredentialsProvider )
125
+ fun clear (resource : Resource <* >, connectionSettings : ConnectionSettings )
120
126
121
127
companion object {
122
128
@JvmStatic
123
- fun getInstance (project : Project ): AwsResourceCache = ServiceManager .getService(project, AwsResourceCache ::class .java)
124
-
125
- // Getting resources can take a long time on a slow connection or if there are a lot of resources. This call should
126
- // always be done in an async context so it should be OK to take multiple seconds.
127
- private val DEFAULT_TIMEOUT = Duration .ofSeconds(30 )
129
+ fun getInstance (): AwsResourceCache = service()
128
130
129
131
private fun <T > wait (timeout : Duration , call : () -> CompletionStage <T >) = try {
130
132
call().toCompletableFuture().get(timeout.toMillis(), TimeUnit .MILLISECONDS )
@@ -134,16 +136,55 @@ interface AwsResourceCache {
134
136
}
135
137
}
136
138
137
- fun <T > Project.getResource (resource : Resource <T >, useStale : Boolean = true, forceFetch : Boolean = false) =
138
- AwsResourceCache .getInstance(this ).getResource(resource, useStale, forceFetch)
139
+ /* *
140
+ * Get a [resource] either by making a call or returning it from the cache if present and unexpired. Uses the currently [AwsRegion]
141
+ * & [ToolkitCredentialsProvider] active in [AwsConnectionManager].
142
+ *
143
+ * @param[useStale] if an exception occurs attempting to refresh the resource return a cached version if it exists (even if it's expired). Default: true
144
+ * @param[forceFetch] force the resource to refresh (and update cache) even if a valid cache version exists. Default: false
145
+ */
146
+ fun <T > Project.getResource (resource : Resource <T >, useStale : Boolean = true, forceFetch : Boolean = false): CompletionStage <T > =
147
+ AwsResourceCache .getInstance().getResource(resource, this .getConnectionSettings(), useStale, forceFetch)
148
+
149
+ /* *
150
+ * Blocking version of [getResource]
151
+ *
152
+ * @param[useStale] if an exception occurs attempting to refresh the resource return a cached version if it exists (even if it's expired). Default: true
153
+ * @param[forceFetch] force the resource to refresh (and update cache) even if a valid cache version exists. Default: false
154
+ */
155
+ fun <T > Project.getResourceNow (resource : Resource <T >, timeout : Duration = DEFAULT_TIMEOUT , useStale : Boolean = true, forceFetch : Boolean = false): T =
156
+ AwsResourceCache .getInstance().getResourceNow(resource, this .getConnectionSettings(), timeout, useStale, forceFetch)
157
+
158
+ /* *
159
+ * Gets the [resource] if it exists in the cache.
160
+ *
161
+ * @param[useStale] return a cached version if it exists (even if it's expired). Default: true
162
+ */
163
+ fun <T > Project.getResourceIfPresent (resource : Resource <T >, useStale : Boolean = true): T ? =
164
+ AwsResourceCache .getInstance().getResourceIfPresent(resource, this .getConnectionSettings(), useStale)
165
+
166
+ /* *
167
+ * Clears the contents of the cache for the specific [resource] type, in the currently active [ConnectionSettings]
168
+ */
169
+ fun Project.clearResourceForCurrentConnection (resource : Resource <* >) =
170
+ AwsResourceCache .getInstance().clear(resource, this .getConnectionSettings())
171
+
172
+ /* *
173
+ * Clears the contents of the cache of all resource types for the currently active [ConnectionSettings]
174
+ */
175
+ fun Project.clearResourceForCurrentConnection () =
176
+ AwsResourceCache .getInstance().clear(this .getConnectionSettings())
177
+
178
+ private fun Project.getConnectionSettings (): ConnectionSettings = AwsConnectionManager .getInstance(this ).connectionSettings()
179
+ ? : throw IllegalStateException (" Bug: ResourceCache was accessed with invalid ConnectionSettings" )
139
180
140
181
sealed class Resource <T > {
141
182
142
183
/* *
143
184
* A [Cached] resource is one whose fetch is potentially expensive, the result of which should be memoized for a period of time ([expiry]).
144
185
*/
145
186
abstract class Cached <T > : Resource <T >() {
146
- abstract fun fetch (project : Project , region : AwsRegion , credentials : ToolkitCredentialsProvider ): T
187
+ abstract fun fetch (region : AwsRegion , credentials : ToolkitCredentialsProvider ): T
147
188
open fun expiry (): Duration = DEFAULT_EXPIRY
148
189
abstract val id: String
149
190
@@ -178,7 +219,7 @@ class ClientBackedCachedResource<ReturnType, ClientType : SdkClient>(
178
219
179
220
constructor (sdkClientClass: KClass <ClientType >, id: String , fetchCall: ClientType .() -> ReturnType ) : this (sdkClientClass, id, null , fetchCall)
180
221
181
- override fun fetch (project : Project , region : AwsRegion , credentials : ToolkitCredentialsProvider ): ReturnType {
222
+ override fun fetch (region : AwsRegion , credentials : ToolkitCredentialsProvider ): ReturnType {
182
223
val client = AwsClientManager .getInstance().getClient(sdkClientClass, credentials, region)
183
224
return fetchCall(client)
184
225
}
@@ -194,7 +235,7 @@ class ExecutableBackedCacheResource<ReturnType, ExecType : ExecutableType<*>>(
194
235
private val fetchCall : GeneralCommandLine .() -> ReturnType
195
236
) : Resource.Cached<ReturnType>() {
196
237
197
- override fun fetch (project : Project , region : AwsRegion , credentials : ToolkitCredentialsProvider ): ReturnType {
238
+ override fun fetch (region : AwsRegion , credentials : ToolkitCredentialsProvider ): ReturnType {
198
239
val executableType = ExecutableType .getExecutable(executableTypeClass.java)
199
240
200
241
val executable = ExecutableManager .getInstance().getExecutableIfPresent(executableType).let {
@@ -217,27 +258,22 @@ class ExecutableBackedCacheResource<ReturnType, ExecType : ExecutableType<*>>(
217
258
}
218
259
219
260
class DefaultAwsResourceCache (
220
- private val project : Project ,
221
261
private val clock : Clock ,
222
262
private val maximumCacheEntries : Int ,
223
263
private val maintenanceInterval : Duration
224
264
) : AwsResourceCache, Disposable, ToolkitCredentialsChangeListener {
225
265
226
266
@Suppress(" unused" )
227
- constructor (project : Project ) : this (project, Clock .systemDefaultZone(), MAXIMUM_CACHE_ENTRIES , DEFAULT_MAINTENANCE_INTERVAL )
267
+ constructor () : this (Clock .systemDefaultZone(), MAXIMUM_CACHE_ENTRIES , DEFAULT_MAINTENANCE_INTERVAL )
228
268
229
269
private val cache = ConcurrentHashMap <CacheKey , Entry <* >>()
230
- private val accountSettings by lazy { AwsConnectionManager .getInstance(project) }
231
270
private val alarm = AlarmFactory .getInstance().create(Alarm .ThreadToUse .POOLED_THREAD , this )
232
271
233
272
init {
234
273
ApplicationManager .getApplication().messageBus.connect(this ).subscribe(CredentialManager .CREDENTIALS_CHANGED , this )
235
274
scheduleCacheMaintenance()
236
275
}
237
276
238
- override fun <T > getResource (resource : Resource <T >, useStale : Boolean , forceFetch : Boolean ) =
239
- getResource(resource, accountSettings.activeRegion, accountSettings.activeCredentialProvider, useStale, forceFetch)
240
-
241
277
override fun <T > getResource (
242
278
resource : Resource <T >,
243
279
region : AwsRegion ,
@@ -286,9 +322,6 @@ class DefaultAwsResourceCache(
286
322
}
287
323
}
288
324
289
- override fun <T > getResourceIfPresent (resource : Resource <T >, useStale : Boolean ): T ? =
290
- getResourceIfPresent(resource, accountSettings.activeRegion, accountSettings.activeCredentialProvider, useStale)
291
-
292
325
override fun <T > getResourceIfPresent (resource : Resource <T >, region : AwsRegion , credentialProvider : ToolkitCredentialsProvider , useStale : Boolean ): T ? =
293
326
when (resource) {
294
327
is Resource .Cached <T > -> {
@@ -301,21 +334,21 @@ class DefaultAwsResourceCache(
301
334
is Resource .View <* , T > -> getResourceIfPresent(resource.underlying, region, credentialProvider, useStale)?.let { resource.doMap(it) }
302
335
}
303
336
304
- override fun clear (resource : Resource <* >) {
305
- clear(resource, accountSettings.activeRegion, accountSettings.activeCredentialProvider)
306
- }
307
-
308
- override fun clear (resource : Resource <* >, region : AwsRegion , credentialProvider : ToolkitCredentialsProvider ) {
337
+ override fun clear (resource : Resource <* >, connectionSettings : ConnectionSettings ) {
309
338
when (resource) {
310
- is Resource .Cached <* > -> cache.remove(CacheKey (resource.id, region.id, credentialProvider .id))
311
- is Resource .View <* , * > -> clear(resource.underlying, region, credentialProvider )
339
+ is Resource .Cached <* > -> cache.remove(CacheKey (resource.id, connectionSettings. region.id, connectionSettings.credentials .id))
340
+ is Resource .View <* , * > -> clear(resource.underlying, connectionSettings )
312
341
}
313
342
}
314
343
315
344
override fun clear () {
316
345
cache.clear()
317
346
}
318
347
348
+ override fun clear (connectionSettings : ConnectionSettings ) {
349
+ cache.keys.removeIf { it.credentialsId == connectionSettings.credentials.id && it.regionId == connectionSettings.region.id }
350
+ }
351
+
319
352
override fun dispose () {
320
353
clear()
321
354
}
@@ -343,7 +376,7 @@ class DefaultAwsResourceCache(
343
376
}
344
377
345
378
private fun <T > fetch (context : Context <T >): Entry <T > {
346
- val value = context.resource.fetch(project, context.region, context.credentials)
379
+ val value = context.resource.fetch(context.region, context.credentials)
347
380
return Entry (clock.instant().plus(context.resource.expiry()), value)
348
381
}
349
382
0 commit comments