001package com.nimbusds.infinispan.persistence.dynamodb; 002 003 004import java.security.InvalidKeyException; 005import java.security.NoSuchAlgorithmException; 006import java.time.Instant; 007import java.util.concurrent.Executor; 008import java.util.function.Predicate; 009 010import com.amazonaws.ClientConfiguration; 011import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; 012import com.amazonaws.client.builder.AwsClientBuilder; 013import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; 014import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; 015import com.amazonaws.services.dynamodbv2.document.DynamoDB; 016import com.amazonaws.services.dynamodbv2.document.Index; 017import com.amazonaws.services.dynamodbv2.document.Item; 018import com.amazonaws.services.dynamodbv2.document.Table; 019import com.amazonaws.services.dynamodbv2.document.spec.GetItemSpec; 020import com.amazonaws.services.dynamodbv2.model.*; 021import com.codahale.metrics.Timer; 022import io.reactivex.Flowable; 023import net.jcip.annotations.ThreadSafe; 024import org.infinispan.commons.configuration.ConfiguredBy; 025import org.infinispan.commons.persistence.Store; 026import org.infinispan.marshall.core.MarshalledEntry; 027import org.infinispan.marshall.core.MarshalledEntryFactory; 028import org.infinispan.persistence.spi.InitializationContext; 029import org.infinispan.persistence.spi.PersistenceException; 030import org.kohsuke.MetaInfServices; 031import org.reactivestreams.Publisher; 032 033import com.nimbusds.infinispan.persistence.common.InfinispanEntry; 034import com.nimbusds.infinispan.persistence.common.InfinispanStore; 035import com.nimbusds.infinispan.persistence.common.query.QueryExecutor; 036import com.nimbusds.infinispan.persistence.dynamodb.config.DynamoDBStoreConfiguration; 037import com.nimbusds.infinispan.persistence.dynamodb.logging.Loggers; 038import com.nimbusds.infinispan.persistence.dynamodb.query.DynamoDBQueryExecutor; 039import com.nimbusds.infinispan.persistence.dynamodb.query.DynamoDBQueryExecutorInitContext; 040 041 042/** 043 * AWS DynamoDB store for Infinispan 9.3+ caches and maps. 044 */ 045@ThreadSafe 046@MetaInfServices 047@ConfiguredBy(DynamoDBStoreConfiguration.class) 048@Store(shared = true) 049public class DynamoDBStore<K,V> extends InfinispanStore<K,V> { 050 051 052 /** 053 * The DynamoDB configuration. 054 */ 055 private DynamoDBStoreConfiguration config; 056 057 058 /** 059 * The DynamoDB HTTP client. 060 */ 061 private AmazonDynamoDB client; 062 063 064 /** 065 * The DynamoDB table API client. 066 */ 067 private Table table; 068 069 070 /** 071 * The DynamoDB item transformer (to / from Infinispan entries). 072 */ 073 private DynamoDBItemTransformer<K,V> itemTransformer; 074 075 076 /** 077 * The optional DynamoDB query executor. 078 */ 079 private DynamoDBQueryExecutor<K,V> queryExecutor; 080 081 082 /** 083 * The DynamoDB request factory. 084 */ 085 private RequestFactory<K,V> requestFactory; 086 087 088 /** 089 * Optional item HMAC SHA-256 security. 090 */ 091 private ItemHMAC itemHMAC; 092 093 094 /** 095 * The marshalled Infinispan entry factory. 096 */ 097 private MarshalledEntryFactory<K,V> marshalledEntryFactory; 098 099 100 /** 101 * Purges expired entries found in the DynamoDB store, as indicated by 102 * their persisted metadata (optional, may be ignored / not stored). 103 */ 104 private ExpiredEntryReaper<K,V> reaper; 105 106 107 /** 108 * DynamoDB meters. 109 */ 110 private DynamoDBMeters meters; 111 112 113 /** 114 * Loads a DynamoDB item transformer with the specified class name. 115 * 116 * @param clazz The class. Must not be {@code null}. 117 * 118 * @return The DynamoDB entry transformer. 119 */ 120 private DynamoDBItemTransformer<K,V> loadItemTransformerClass(final Class<?> clazz) { 121 122 try { 123 @SuppressWarnings( "unchecked" ) 124 Class<DynamoDBItemTransformer<K,V>> genClazz = (Class<DynamoDBItemTransformer<K,V>>)clazz; 125 return genClazz.newInstance(); 126 } catch (Exception e) { 127 throw new PersistenceException("Couldn't load DynamoDB item transformer class: " + e.getMessage(), e); 128 } 129 } 130 131 132 /** 133 * Loads a DynamoDB query executor with the specified class name. 134 * 135 * @param clazz The class. Must not be {@code null}. 136 * 137 * @return The DynamoDB query executor. 138 */ 139 private DynamoDBQueryExecutor<K,V> loadQueryExecutorClass(final Class<?> clazz) { 140 141 try { 142 @SuppressWarnings( "unchecked" ) 143 Class<DynamoDBQueryExecutor<K,V>> genClazz = (Class<DynamoDBQueryExecutor<K,V>>)clazz; 144 return genClazz.newInstance(); 145 } catch (Exception e) { 146 throw new PersistenceException("Couldn't load DynamoDB query executor class: " + e.getMessage(), e); 147 } 148 } 149 150 151 /** 152 * Returns the DynamoDB store configuration. 153 * 154 * @return The DynamoDB store configuration, {@code null} if not 155 * initialised. 156 */ 157 public DynamoDBStoreConfiguration getConfiguration() { 158 return config; 159 } 160 161 162 /** 163 * Returns the underlying DynamoDB table. 164 * 165 * @return The DynamoDB table, {@code null} if not initialised. 166 */ 167 public Table getTable() { 168 return table; 169 } 170 171 172 /** 173 * Returns the configured DynamoDB item transformer. 174 * 175 * @return The DynamoDB item transformer, {@code null} if not 176 * initialised. 177 */ 178 public DynamoDBItemTransformer<K, V> getItemTransformer() { 179 return itemTransformer; 180 } 181 182 183 @Override 184 public QueryExecutor<K, V> getQueryExecutor() { 185 return queryExecutor; 186 } 187 188 189 /** 190 * Returns the DynamoDB timers. 191 * 192 * @return The timers, {@code null} if not initialised. 193 */ 194 public DynamoDBMeters getMeters() { 195 return meters; 196 } 197 198 199 @Override 200 @SuppressWarnings("unchecked") 201 public void init(final InitializationContext ctx) { 202 203 // This method will be invoked by the PersistenceManager during initialization. The InitializationContext 204 // contains: 205 // - this CacheLoader's configuration 206 // - the cache to which this loader is applied. Your loader might want to use the cache's name to construct 207 // cache-specific identifiers 208 // - the StreamingMarshaller that needs to be used to marshall/unmarshall the entries 209 // - a TimeService which the loader can use to determine expired entries 210 // - a ByteBufferFactory which needs to be used to construct ByteBuffers 211 // - a MarshalledEntryFactory which needs to be used to construct entries from the data retrieved by the loader 212 213 super.init(ctx); 214 215 this.config = ctx.getConfiguration(); 216 217 // Log the configuration 218 Loggers.MAIN_LOG.info("[DS0100] DynamoDB store: Infinispan cache store configuration for {}:", getCacheName()); 219 config.log(); 220 221 // Create a DynamoDB client 222 AmazonDynamoDBClientBuilder builder = AmazonDynamoDBClientBuilder 223 .standard() 224 .withCredentials(DefaultAWSCredentialsProviderChain.getInstance()); 225 226 if (config.getEndpoint() != null && ! config.getEndpoint().trim().isEmpty()) { 227 // If endpoint is set, override region 228 builder = builder.withEndpointConfiguration( 229 new AwsClientBuilder.EndpointConfiguration( 230 config.getEndpoint(), 231 null // inferred from endpoint 232 ) 233 ); 234 } else { 235 builder.withRegion(config.getRegion()); 236 } 237 238 if (config.getHTTPProxyHost() != null) { 239 ClientConfiguration clientConfig = new ClientConfiguration().withProxyHost(config.getHTTPProxyHost()); 240 if (config.getHTTPProxyPort() > -1) { 241 clientConfig.withProxyPort(config.getHTTPProxyPort()); 242 } 243 builder = builder.withClientConfiguration(clientConfig); 244 } 245 246 try { 247 itemHMAC = new ItemHMAC(config.getHMACSHA256Key()); 248 } catch (InvalidKeyException e) { 249 throw new PersistenceException(e.getMessage(), e); 250 } 251 252 client = builder.build(); 253 254 Loggers.MAIN_LOG.info("[DS0140] DynamoDB store: Expiration thread wake up interval for cache {}: {}", getCacheName(), 255 ctx.getCache().getCacheConfiguration().expiration().wakeUpInterval()); 256 257 // Load and initialise the DynamoDB item transformer 258 Loggers.MAIN_LOG.debug("[DS0101] Loading DynamoDB item transformer class {} for cache {}...", 259 config.getItemTransformerClass(), 260 getCacheName()); 261 262 itemTransformer = loadItemTransformerClass(config.getItemTransformerClass()); 263 itemTransformer.init(() -> config.isEnableTTL()); 264 265 requestFactory = new RequestFactory<>( 266 itemTransformer, 267 config.getIndexedAttributes(), 268 config.isConsistentReads(), 269 config.getProvisionedThroughput(), 270 config.isTableWithEncryptionAtRest(), 271 config.getTablePrefix(), 272 config.getApplyRangeKey(), 273 config.getRangeKeyValue(), 274 config.isEnableStream()); 275 276 table = new DynamoDB(client).getTable(requestFactory.getTableName()); 277 278 // Load and initialise the optional query executor 279 if (config.getQueryExecutorClass() != null) { 280 Loggers.MAIN_LOG.debug("[DS0130] Loading optional DynamoDB query executor class {} for cache {}...", 281 config.getQueryExecutorClass(), 282 getCacheName()); 283 284 queryExecutor = loadQueryExecutorClass(config.getQueryExecutorClass()); 285 286 queryExecutor.init(new DynamoDBQueryExecutorInitContext<K, V>() { 287 @Override 288 public DynamoDBItemTransformer<K,V> getDynamoDBItemTransformer() { 289 return itemTransformer; 290 } 291 292 @Override 293 public Table getDynamoDBTable() { 294 return table; 295 } 296 297 @Override 298 public Index getDynamoDBIndex(final String attributeName) { 299 if (config.getIndexedAttributes() != null && config.getIndexedAttributes().contains(attributeName)) { 300 return table.getIndex(requestFactory.getGSIName(attributeName)); 301 } else { 302 return null; 303 } 304 } 305 }); 306 } 307 308 marshalledEntryFactory = (MarshalledEntryFactory<K,V>)ctx.getMarshalledEntryFactory(); 309 310 final String metricsPrefix = ctx.getCache().getName() + "."; 311 if (config.getMetricRegistry() == null) 312 meters = new DynamoDBMeters(metricsPrefix); 313 else 314 meters = new DynamoDBMeters(metricsPrefix, config.getMetricRegistry()); 315 316 Loggers.MAIN_LOG.info("[DS0102] Initialized DynamoDB store for cache {} with table {}", 317 getCacheName(), 318 table.getTableName()); 319 } 320 321 322 @Override 323 public void start() { 324 325 // This method will be invoked by the PersistenceManager to start the CacheLoader. At this stage configuration 326 // is complete and the loader can perform operations such as opening a connection to the external storage, 327 // initialize internal data structures, etc. 328 329 CreateTableRequest ctr = requestFactory.resolveCreateTableRequest(); 330 331 try { 332 table = new DynamoDB(client) 333 .createTable( 334 ctr.withProvisionedThroughput(config.getProvisionedThroughput()) 335 ); 336 337 Loggers.MAIN_LOG.info("[DS0129] DynamoDB store: Created table {} for cache {}", table.getTableName(), getCacheName()); 338 339 } catch (ResourceInUseException e) { 340 // table exists 341 Loggers.MAIN_LOG.info("[DS0133] DynamoDB store: Table {} for cache {} exists", table.getTableName(), getCacheName()); 342 } catch (Exception e) { 343 Loggers.MAIN_LOG.fatal("[DS0103] DynamoDB store: Couldn't create table {} with {}: {}: {}", table.getTableName(), ctr, e.getMessage(), e); 344 throw new PersistenceException(e.getMessage(), e); 345 } 346 347 try { 348 table.waitForActive(); 349 } catch (InterruptedException e) { 350 throw new PersistenceException("Interrupted while awaiting DynamoDB table " + table.getTableName() + " to become active: " + e.getMessage(), e); 351 } 352 353 Loggers.MAIN_LOG.info("[DS0141] DynamoDB store: Table properties: " + table.getDescription()); 354 355 356 ContinuousBackupsStatus contBackupStatus = null; 357 358 try { 359 // Not available on localhost DynamoDB 360 DescribeContinuousBackupsResult result = client.describeContinuousBackups(new DescribeContinuousBackupsRequest().withTableName(table.getTableName())); 361 contBackupStatus = ContinuousBackupsStatus.fromValue(result.getContinuousBackupsDescription().getContinuousBackupsStatus()); 362 Loggers.MAIN_LOG.info("[DS0143] DynamoDB store: Continuous backup status for table {}: {}", table.getTableName(), contBackupStatus); 363 Loggers.MAIN_LOG.info("[DS0144] DynamoDB store: Point in time recovery status for table {}: {}", 364 table.getTableName(), 365 result.getContinuousBackupsDescription().getPointInTimeRecoveryDescription().getPointInTimeRecoveryStatus()); 366 } catch (Exception e) { 367 Loggers.MAIN_LOG.error("[DS0145] DynamoDB store: Couldn't obtain continuous backup status for table {}: {}", table.getTableName(), e.getMessage()); 368 } 369 370 if (ContinuousBackupsStatus.DISABLED.equals(contBackupStatus) && config.isEnableContinuousBackups()) { 371 372 try { 373 client.updateContinuousBackups( 374 new UpdateContinuousBackupsRequest() 375 .withTableName(table.getTableName()) 376 .withPointInTimeRecoverySpecification( 377 new PointInTimeRecoverySpecification() 378 .withPointInTimeRecoveryEnabled(true))); 379 380 } catch (Exception e) { 381 String msg = "Couldn't set up continuous backups for table " + table.getTableName() + ": " + e.getMessage(); 382 Loggers.MAIN_LOG.fatal("[DS0104] DynamoDB store: {}", msg); 383 throw new PersistenceException(msg, e); 384 } 385 } 386 387 TimeToLiveStatus currentTTLStatus = null; 388 389 try { 390 // Not available on localhost DynamoDB 391 DescribeTimeToLiveResult result = client.describeTimeToLive(new DescribeTimeToLiveRequest().withTableName(table.getTableName())); 392 currentTTLStatus = TimeToLiveStatus.fromValue(result.getTimeToLiveDescription().getTimeToLiveStatus()); 393 Loggers.MAIN_LOG.info("[DS0161] DynamoDB store: TTL for table {}: status={} attribute={}", 394 table.getTableName(), 395 currentTTLStatus, 396 result.getTimeToLiveDescription().getAttributeName()); 397 } catch (Exception e) { 398 Loggers.MAIN_LOG.error("[DS0162] DynamoDB store: Couldn't obtain TTL status for table {}: {}", table.getTableName(), e.getMessage()); 399 } 400 401 if (TimeToLiveStatus.DISABLED.equals(currentTTLStatus) && config.isEnableTTL() && requestFactory.getItemTransformer().getTTLAttributeName() != null) { 402 try { 403 client.updateTimeToLive( 404 new UpdateTimeToLiveRequest() 405 .withTableName(table.getTableName()) 406 .withTimeToLiveSpecification( 407 new TimeToLiveSpecification() 408 .withAttributeName(requestFactory.getItemTransformer().getTTLAttributeName()) 409 .withEnabled(true))); 410 } catch (Exception e) { 411 String msg = "Couldn't set up TTL for table " + table.getTableName() + ": " + e.getMessage(); 412 Loggers.MAIN_LOG.fatal("[DS0160] DynamoDB store: {}", msg); 413 throw new PersistenceException(msg, e); 414 } 415 } 416 417 reaper = new ExpiredEntryReaper<>(marshalledEntryFactory, table, requestFactory, config.getPurgeLimit(), meters.purgeTimer); 418 419 Loggers.MAIN_LOG.info("[DS0104] Started DynamoDB external store connector for cache {} with table {}", getCacheName(), table.getTableName()); 420 } 421 422 423 @Override 424 public void stop() { 425 426 super.stop(); 427 428 if (client != null) { 429 client.shutdown(); 430 } 431 432 Loggers.MAIN_LOG.info("[DS0105] Stopped DynamoDB store connector for cache {}", getCacheName()); 433 } 434 435 436 @Override 437 public boolean contains(final Object key) { 438 439 // This method will be invoked by the PersistenceManager to determine if the loader contains the specified key. 440 // The implementation should be as fast as possible, e.g. it should strive to transfer the least amount of data possible 441 // from the external storage to perform the check. Also, if possible, make sure the field is indexed on the external storage 442 // so that its existence can be determined as quickly as possible. 443 // 444 // Note that keys will be in the cache's native format, which means that if the cache is being used by a remoting protocol 445 // such as HotRod or REST and compatibility mode has not been enabled, then they will be encoded in a byte[]. 446 447 Loggers.DYNAMODB_LOG.trace("[DS0106] DynamoDB store: Checking {} cache key {}", getCacheName(), key); 448 449 Timer.Context timerCtx = meters.getTimer.time(); 450 451 try { 452 return table.getItem(requestFactory.resolveGetItemSpec(key)) != null; 453 454 } catch (Exception e) { 455 Loggers.DYNAMODB_LOG.error("[DS0107] {}: {}", e.getMessage(), e); 456 throw new PersistenceException(e.getMessage(), e); 457 } finally { 458 timerCtx.stop(); 459 } 460 } 461 462 463 @Override 464 public MarshalledEntry<K,V> load(final Object key) { 465 466 // Fetches an entry from the storage using the specified key. The CacheLoader should retrieve from the external storage all 467 // of the data that is needed to reconstruct the entry in memory, i.e. the value and optionally the metadata. This method 468 // needs to return a MarshalledEntry which can be constructed as follows: 469 // 470 // ctx.getMarshalledEntryFactory().new MarshalledEntry(key, value, metadata); 471 // 472 // If the entry does not exist or has expired, this method should return null. 473 // If an error occurs while retrieving data from the external storage, this method should throw a PersistenceException 474 // 475 // Note that keys and values will be in the cache's native format, which means that if the cache is being used by a remoting protocol 476 // such as HotRod or REST and compatibility mode has not been enabled, then they will be encoded in a byte[]. 477 // If the loader needs to have knowledge of the key/value data beyond their binary representation, then it needs access to the key's and value's 478 // classes and the marshaller used to encode them. 479 480 Loggers.DYNAMODB_LOG.trace("[DS0108] DynamoDB store: Loading {} cache entry with key {}", getCacheName(), key); 481 482 Item item; 483 484 Timer.Context timerCtx = meters.getTimer.time(); 485 486 GetItemSpec getItemSpec = requestFactory.resolveGetItemSpec(key); 487 488 try { 489 item = table.getItem(getItemSpec); 490 491 } catch (Exception e) { 492 Loggers.DYNAMODB_LOG.error("[DS0109] {}, {}", e.getMessage(), e); 493 throw new PersistenceException(e.getMessage(), e); 494 } finally { 495 timerCtx.stop(); 496 } 497 498 if (item == null) { 499 // Not found 500 Loggers.DYNAMODB_LOG.trace("[DS0110] DynamoDB store: Item with key {} not found", key); 501 return null; 502 } 503 504 Loggers.DYNAMODB_LOG.trace("[DS0111] DynamoDB store: Retrieved {} cache item: {}", getCacheName(), item); 505 506 try { 507 item = itemHMAC.verify(item); 508 } catch (InvalidHMACException e) { 509 meters.invalidItemHmacCounter.inc(); 510 Loggers.DYNAMODB_LOG.error("[DS0131] DynamoDB store: Invalid item HMAC in {}: {}", getCacheName(), e.getMessage()); 511 throw new PersistenceException(e.getMessage(), e); 512 } catch (NoSuchAlgorithmException | InvalidKeyException e) { 513 throw new PersistenceException(e.getMessage(), e); 514 } 515 516 // Transform DynamoDB entry to Infinispan entry 517 InfinispanEntry<K,V> infinispanEntry = itemTransformer.toInfinispanEntry(item); 518 519 if (infinispanEntry.isExpired()) { 520 Loggers.DYNAMODB_LOG.trace("[DS0114] DynamoDB store: Item with key {} expired", key); 521 return null; 522 } 523 524 return marshalledEntryFactory.newMarshalledEntry( 525 infinispanEntry.getKey(), 526 infinispanEntry.getValue(), 527 infinispanEntry.getMetadata()); 528 } 529 530 531 @Override 532 public boolean delete(final Object key) { 533 534 // The CacheWriter should remove from the external storage the entry identified by the specified key. 535 // Note that keys will be in the cache's native format, which means that if the cache is being used by a remoting protocol 536 // such as HotRod or REST and compatibility mode has not been enabled, then they will be encoded in a byte[]. 537 538 Loggers.DYNAMODB_LOG.trace("[DS0112] DynamoDB store: Deleting {} cache entry with key {}", getCacheName(), key); 539 540 final boolean deleted; 541 542 Timer.Context timerCtx = meters.deleteTimer.time(); 543 544 try { 545 deleted = table.deleteItem(requestFactory.resolveDeleteItemSpec(key)).getItem() != null; 546 547 } catch (Exception e) { 548 Loggers.DYNAMODB_LOG.error("[DS0113] {}, {}", e.getMessage(), e); 549 throw new PersistenceException(e.getMessage(), e); 550 } finally { 551 timerCtx.stop(); 552 } 553 554 if (deleted) { 555 Loggers.DYNAMODB_LOG.trace("[DS0116] DynamoDB store: Deleted {} cache item with key {}", getCacheName(), key); 556 } 557 558 return deleted; 559 } 560 561 562 @Override 563 public void write(final MarshalledEntry<? extends K, ? extends V> marshalledEntry) { 564 565 // The CacheWriter should write the specified entry to the external storage. 566 // 567 // The PersistenceManager uses MarshalledEntry as the default format so that CacheWriters can efficiently store data coming 568 // from a remote node, thus avoiding any additional transformation steps. 569 // 570 // Note that keys and values will be in the cache's native format, which means that if the cache is being used by a remoting protocol 571 // such as HotRod or REST and compatibility mode has not been enabled, then they will be encoded in a byte[]. 572 573 Loggers.DYNAMODB_LOG.trace("[DS0115] DynamoDB store: Writing {} cache entry {}", getCacheName(), marshalledEntry); 574 575 Timer.Context timerCtx = meters.putTimer.time(); 576 577 try { 578 Item item = requestFactory.resolveItem( 579 new InfinispanEntry<>( 580 marshalledEntry.getKey(), 581 marshalledEntry.getValue(), 582 marshalledEntry.getMetadata())); 583 584 item = itemHMAC.apply(item); 585 586 Loggers.DYNAMODB_LOG.trace("[DS0132] DynamoDB store: Writing {} cache item: {}", getCacheName(), item); 587 588 table.putItem(item); 589 590 } catch (Exception e) { 591 Loggers.DYNAMODB_LOG.error("[DS0117] {}: {}", e.getMessage(), e); 592 throw new PersistenceException(e.getMessage(), e); 593 } finally { 594 timerCtx.stop(); 595 } 596 } 597 598 @Override 599 public Publisher<MarshalledEntry<K, V>> publishEntries(final Predicate<? super K> filter, final boolean fetchValue, final boolean fetchMetadata) { 600 601 Loggers.DYNAMODB_LOG.trace("[DS0118] DynamoDB store: Processing key filter for {} cache: fetchValue={} fetchMetadata={}", 602 getCacheName(), fetchValue, fetchMetadata); 603 604 final Instant now = Instant.now(); 605 606 return Flowable.using(meters.processTimer::time, 607 ignore -> Flowable.fromIterable(requestFactory.getAllItems(table)) 608 .map(itemTransformer::toInfinispanEntry) 609 .filter(infinispanEntry -> filter == null || filter.test(infinispanEntry.getKey())) 610 .filter(infinispanEntry -> ! infinispanEntry.isExpired(now)) 611 .map(infinispanEntry -> marshalledEntryFactory.newMarshalledEntry( 612 infinispanEntry.getKey(), 613 infinispanEntry.getValue(), 614 infinispanEntry.getMetadata())) 615 .doOnError(e -> Loggers.DYNAMODB_LOG.error("[DS0119] {}: {}", e.getMessage(), e)), 616 Timer.Context::stop); 617 } 618 619 @Override 620 public int size() { 621 622 // Infinispan code analysis on 8.2 and 9.4.11 shows that this method is never called in practice, and 623 // is not wired to the data / cache container API 624 625 // TODO inaccurate when a range key is applied! 626 627 Loggers.DYNAMODB_LOG.trace("[DS0120] DynamoDB store: Counting {} cache items", getCacheName()); 628 629 final int count; 630 631 try { 632 count = table.describe().getItemCount().intValue(); 633 634 } catch (Exception e) { 635 Loggers.DYNAMODB_LOG.error("[DS0121] {}: {}", e.getMessage(), e); 636 throw new PersistenceException(e.getMessage(), e); 637 } 638 639 Loggers.DYNAMODB_LOG.trace("[DS0122] DynamoDB store: Reported approximately {} {} items", count, getCacheName()); 640 641 return count; 642 } 643 644 645 @Override 646 public void clear() { 647 648 Loggers.DYNAMODB_LOG.trace("[DS0123] DynamoDB store: Clearing {} items", getCacheName()); 649 650 if (requestFactory.getRangeKeyResolvedName() != null) { 651 throw new PersistenceException("DynamoDB clear operation not supported with applied range key"); 652 } 653 654 try { 655 DeleteTableResult result = table.delete(); 656 657 int numDeleted = result.getTableDescription().getItemCount().intValue(); 658 659 Loggers.DYNAMODB_LOG.info("[DS0125] DynamoDB store: Cleared {} {} items", numDeleted, table.getTableName()); 660 661 table.waitForDelete(); 662 663 client.createTable(requestFactory.resolveCreateTableRequest()); 664 665 table.waitForActive(); 666 667 } catch (Exception e) { 668 Loggers.DYNAMODB_LOG.error("[DS0124] {}: {}", e.getMessage(), e); 669 throw new PersistenceException(e.getMessage(), e); 670 } 671 } 672 673 674 @Override 675 public void purge(final Executor executor, final PurgeListener<? super K> purgeListener) { 676 677 Loggers.DYNAMODB_LOG.trace("[DS0126] DynamoDB store: Purging {} cache entries", getCacheName()); 678 679 try { 680 executor.execute(() -> reaper.purge(purgeListener)); 681 682 } catch (Exception e) { 683 Loggers.DYNAMODB_LOG.error("[DS0127] {}: {}", e.getMessage(), e); 684 throw new PersistenceException("Purge exception: " + e.getMessage(), e); 685 } 686 } 687 688 689 @Override 690 public void purge(final Executor executor, final ExpirationPurgeListener<K,V> purgeListener) { 691 692 Loggers.DYNAMODB_LOG.trace("[DS0150] DynamoDB store: Purging {} cache entries", getCacheName()); 693 694 try { 695 executor.execute(() -> reaper.purgeExtended(purgeListener)); 696 697 } catch (Exception e) { 698 Loggers.DYNAMODB_LOG.error("[DS0151] {}: {}", e.getMessage(), e); 699 throw new PersistenceException("Purge exception: " + e.getMessage(), e); 700 } 701 } 702}