|
18 | 18 | import java.util.HashMap; |
19 | 19 | import java.util.List; |
20 | 20 | import java.util.Map; |
| 21 | +import java.util.Objects; |
21 | 22 | import java.util.Optional; |
22 | 23 | import java.util.Set; |
23 | 24 | import java.util.function.Function; |
@@ -234,134 +235,150 @@ public void addIndexers(Map<String, Function<R, List<String>>> indexers) { |
234 | 235 | this.indexers.putAll(indexers); |
235 | 236 | } |
236 | 237 |
|
| 238 | + /** |
| 239 | + * {@inheritDoc} |
| 240 | + * |
| 241 | + * <p>This implementation is read-cache-after-write consistent. Results are merged with the |
| 242 | + * temporary resource cache to ensure recently written resources are reflected in the output. |
| 243 | + */ |
237 | 244 | @Override |
238 | 245 | public Stream<R> list(String namespace, Predicate<R> predicate) { |
239 | | - return manager().list(namespace, predicate); |
| 246 | + return mergeWithTempCacheForList(manager().list(namespace, predicate), namespace, predicate); |
240 | 247 | } |
241 | 248 |
|
| 249 | + /** |
| 250 | + * {@inheritDoc} |
| 251 | + * |
| 252 | + * <p>This implementation is read-cache-after-write consistent. Results are merged with the |
| 253 | + * temporary resource cache to ensure recently written resources are reflected in the output. |
| 254 | + */ |
242 | 255 | @Override |
243 | 256 | public Stream<R> list(Predicate<R> predicate) { |
244 | | - return cache.list(predicate); |
245 | | - } |
246 | | - |
247 | | - @Override |
248 | | - public List<R> byIndex(String indexName, String indexKey) { |
249 | | - return manager().byIndex(indexName, indexKey); |
250 | | - } |
251 | | - |
252 | | - public Stream<R> byIndexStream(String indexName, String indexKey) { |
253 | | - return manager().byIndexStream(indexName, indexKey); |
| 257 | + return mergeWithTempCacheForList(cache.list(predicate), null, predicate); |
254 | 258 | } |
255 | 259 |
|
256 | 260 | /** |
257 | | - * Like {@link #list(String, Predicate)} but for read-cache-after-write consistency. This is |
258 | | - * useful when resources are updated using {@link |
259 | | - * io.javaoperatorsdk.operator.api.reconciler.ResourceOperations}. |
| 261 | + * {@inheritDoc} |
| 262 | + * |
| 263 | + * <p>This implementation is read-cache-after-write consistent. Results are merged with the |
| 264 | + * temporary resource cache to ensure recently written resources are reflected in the output. |
260 | 265 | */ |
261 | | - public Stream<R> listWithStrongConsistency(String namespace, Predicate<R> predicate) { |
262 | | - return mergeWithWithTempCacheResources( |
263 | | - manager().list(namespace, predicate), namespace, predicate); |
| 266 | + @Override |
| 267 | + public Stream<R> byIndexStream(String indexName, String indexKey) { |
| 268 | + return mergeWithTempCacheForIndex( |
| 269 | + manager().byIndexStream(indexName, indexKey), indexName, indexKey); |
264 | 270 | } |
265 | 271 |
|
266 | 272 | /** |
267 | | - * Like {@link #list(Predicate)} but for read-cache-after-write consistency. This is useful when |
268 | | - * resources are updated using {@link |
269 | | - * io.javaoperatorsdk.operator.api.reconciler.ResourceOperations}. |
| 273 | + * {@inheritDoc} |
| 274 | + * |
| 275 | + * <p>This implementation is read-cache-after-write consistent. Results are merged with the |
| 276 | + * temporary resource cache to ensure recently written resources are reflected in the output. |
270 | 277 | */ |
271 | | - public Stream<R> listWithStrongConsistency(Predicate<R> predicate) { |
272 | | - return mergeWithWithTempCacheResources(cache.list(predicate), null, predicate); |
| 278 | + @Override |
| 279 | + public List<R> byIndex(String indexName, String indexKey) { |
| 280 | + return mergeWithTempCacheForIndex( |
| 281 | + manager().byIndexStream(indexName, indexKey), indexName, indexKey) |
| 282 | + .collect(Collectors.toList()); |
273 | 283 | } |
274 | 284 |
|
275 | | - /** |
276 | | - * Like {@link #byIndexStream(String, String)} but for read-cache-after-write consistency. This is |
277 | | - * useful when resources are updated using {@link |
278 | | - * io.javaoperatorsdk.operator.api.reconciler.ResourceOperations}. |
279 | | - */ |
280 | | - public Stream<R> byIndexStreamWithStrongConsistency(String indexName, String indexKey) { |
281 | | - return mergeWithWithTempCacheResources( |
282 | | - manager().byIndexStream(indexName, indexKey), indexName, indexKey); |
283 | | - } |
| 285 | + private Stream<R> mergeWithTempCacheForList( |
| 286 | + Stream<R> stream, String namespace, Predicate<R> predicate) { |
| 287 | + if (!comparableResourceVersions || temporaryResourceCache.isEmpty()) { |
| 288 | + return stream.filter(filterResourceByNamespaceAndPredicate(namespace, predicate)); |
| 289 | + } |
| 290 | + var tempResources = new HashMap<>(temporaryResourceCache.getResources()); |
| 291 | + if (tempResources.isEmpty()) { |
| 292 | + return stream.filter(filterResourceByNamespaceAndPredicate(namespace, predicate)); |
| 293 | + } |
284 | 294 |
|
285 | | - private Stream<R> mergeWithWithTempCacheResources( |
286 | | - Stream<R> stream, String indexName, String indexKey) { |
287 | | - return mergeWithWithTempCacheResources(stream, null, null, indexName, indexKey); |
288 | | - } |
| 295 | + var upToDateList = |
| 296 | + stream |
| 297 | + .map( |
| 298 | + r -> { |
| 299 | + var resourceID = ResourceID.fromResource(r); |
| 300 | + var tempResource = tempResources.remove(resourceID); |
| 301 | + if (tempResource != null |
| 302 | + && ReconcilerUtilsInternal.compareResourceVersions(tempResource, r) > 0) { |
| 303 | + return tempResource; |
| 304 | + } |
| 305 | + return r; |
| 306 | + }) |
| 307 | + .filter(filterResourceByNamespaceAndPredicate(namespace, predicate)) |
| 308 | + .toList(); |
289 | 309 |
|
290 | | - private Stream<R> mergeWithWithTempCacheResources( |
291 | | - Stream<R> stream, String namespace, Predicate<R> predicate) { |
292 | | - return mergeWithWithTempCacheResources(stream, namespace, predicate, null, null); |
| 310 | + return Stream.concat( |
| 311 | + tempResources.values().stream() |
| 312 | + .filter(filterResourceByNamespaceAndPredicate(namespace, predicate)), |
| 313 | + upToDateList.stream()); |
293 | 314 | } |
294 | 315 |
|
295 | | - private Stream<R> mergeWithWithTempCacheResources( |
296 | | - Stream<R> stream, |
297 | | - String namespace, |
298 | | - Predicate<R> predicate, |
299 | | - String indexName, |
300 | | - String indexKey) { |
| 316 | + private Stream<R> mergeWithTempCacheForIndex( |
| 317 | + Stream<R> stream, String indexName, String indexKey) { |
301 | 318 | if (!comparableResourceVersions || temporaryResourceCache.isEmpty()) { |
302 | 319 | return stream; |
303 | 320 | } |
304 | | - var allTempResources = temporaryResourceCache.getResources(); |
305 | | - Map<ResourceID, R> tempResources; |
306 | | - if (namespace == null && predicate == null) { |
307 | | - tempResources = new HashMap<>(allTempResources); |
308 | | - } else { |
309 | | - // filtering the temp cache according the user input (predicate, namespace) |
310 | | - tempResources = |
311 | | - allTempResources.entrySet().stream() |
312 | | - .filter( |
313 | | - e -> { |
314 | | - if (namespace != null) { |
315 | | - var res = |
316 | | - e.getKey().getNamespace().map(ns -> ns.equals(namespace)).orElse(false); |
317 | | - if (!res) return false; |
318 | | - } |
319 | | - if (predicate != null) { |
320 | | - return predicate.test(e.getValue()); |
321 | | - } |
322 | | - return true; |
323 | | - }) |
324 | | - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); |
325 | | - } |
| 321 | + var tempResources = new HashMap<>(temporaryResourceCache.getResources()); |
326 | 322 | if (tempResources.isEmpty()) { |
327 | 323 | return stream; |
328 | 324 | } |
| 325 | + |
| 326 | + var indexer = indexers.get(indexName); |
| 327 | + if (indexer == null) { |
| 328 | + throw new IllegalArgumentException("Indexer not found for: " + indexName); |
| 329 | + } |
| 330 | + |
329 | 331 | var upToDateList = |
330 | 332 | stream |
331 | 333 | .map( |
332 | 334 | r -> { |
333 | 335 | var resourceID = ResourceID.fromResource(r); |
334 | | - // removing the id from the related temp resources |
335 | | - // this is important so we can detect ghost resources: |
336 | | - // all that remains is ghost resource |
337 | 336 | var tempResource = tempResources.remove(resourceID); |
338 | | - // using the latest version |
339 | 337 | if (tempResource != null |
340 | 338 | && ReconcilerUtilsInternal.compareResourceVersions(tempResource, r) > 0) { |
| 339 | + if (!indexer.apply(tempResource).contains(indexKey)) { |
| 340 | + return null; |
| 341 | + } |
341 | 342 | return tempResource; |
342 | 343 | } |
343 | 344 | return r; |
344 | 345 | }) |
| 346 | + .filter(Objects::nonNull) |
345 | 347 | .toList(); |
346 | | - Stream<R> tempResourceStream; |
347 | | - // ghost resource handling |
348 | | - if (indexName != null && indexKey != null) { |
349 | | - var indexer = indexers.get(indexName); |
350 | | - if (indexer == null) { |
351 | | - throw new IllegalArgumentException("Indexer not found for: " + indexName); |
| 348 | + |
| 349 | + // remaining temp resources are ghost resources — include only those matching the index |
| 350 | + return Stream.concat( |
| 351 | + tempResources.values().stream().filter(r -> indexer.apply(r).contains(indexKey)), |
| 352 | + upToDateList.stream()); |
| 353 | + } |
| 354 | + |
| 355 | + private static <R extends HasMetadata> Predicate<R> filterResourceByNamespaceAndPredicate( |
| 356 | + String namespace, Predicate<R> predicate) { |
| 357 | + return r -> { |
| 358 | + if (namespace != null) { |
| 359 | + var res = Optional.of(r).map(ns -> ns.equals(namespace)).orElse(false); |
| 360 | + if (!res) return false; |
352 | 361 | } |
353 | | - // we check if the ghost resource is part of the index |
354 | | - tempResourceStream = |
355 | | - tempResources.values().stream().filter(r -> indexer.apply(r).contains(indexKey)); |
356 | | - } else { |
357 | | - tempResourceStream = tempResources.values().stream(); |
358 | | - } |
359 | | - return Stream.concat(tempResourceStream, upToDateList.stream()); |
| 362 | + if (predicate != null) { |
| 363 | + return predicate.test(r); |
| 364 | + } |
| 365 | + return true; |
| 366 | + }; |
360 | 367 | } |
361 | 368 |
|
| 369 | + /** |
| 370 | + * {@inheritDoc} |
| 371 | + * |
| 372 | + * <p>This implementation is read-cache-after-write consistent. Keys from the temporary resource |
| 373 | + * cache (ghost resources) are included in the result. |
| 374 | + */ |
362 | 375 | @Override |
363 | 376 | public Stream<ResourceID> keys() { |
364 | | - return cache.keys(); |
| 377 | + if (!comparableResourceVersions || temporaryResourceCache.isEmpty()) { |
| 378 | + return manager().keys(); |
| 379 | + } |
| 380 | + var tempKeys = temporaryResourceCache.getResources().keySet(); |
| 381 | + return Stream.concat(manager().keys(), tempKeys.stream().filter(k -> !manager().contains(k))); |
365 | 382 | } |
366 | 383 |
|
367 | 384 | @Override |
|
0 commit comments