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 try { 330 table = new DynamoDB(client).createTable(requestFactory 331 .resolveCreateTableRequest() 332 .withProvisionedThroughput( 333 config.getProvisionedThroughput())); 334 335 Loggers.MAIN_LOG.info("[DS0129] DynamoDB store: Created table {} for cache {}", table.getTableName(), getCacheName()); 336 337 } catch (ResourceInUseException e) { 338 // table exists 339 } catch (Exception e) { 340 Loggers.MAIN_LOG.fatal("[DS0103] DynamoDB store: Couldn't create table {}: {}: {}", table.getTableName(), e.getMessage(), e); 341 throw new PersistenceException(e.getMessage(), e); 342 } 343 344 try { 345 table.waitForActive(); 346 } catch (InterruptedException e) { 347 throw new PersistenceException("Interrupted while awaiting DynamoDB table " + table.getTableName() + " to become active: " + e.getMessage(), e); 348 } 349 350 Loggers.MAIN_LOG.info("[DS0141] DynamoDB store: Table properties: " + table.getDescription()); 351 352 353 ContinuousBackupsStatus contBackupStatus = null; 354 355 try { 356 // Not available on localhost DynamoDB 357 DescribeContinuousBackupsResult result = client.describeContinuousBackups(new DescribeContinuousBackupsRequest().withTableName(table.getTableName())); 358 contBackupStatus = ContinuousBackupsStatus.fromValue(result.getContinuousBackupsDescription().getContinuousBackupsStatus()); 359 Loggers.MAIN_LOG.info("[DS0143] DynamoDB store: Continuous backup status for table {}: {}", table.getTableName(), contBackupStatus); 360 Loggers.MAIN_LOG.info("[DS0144] DynamoDB store: Point in time recovery status for table {}: {}", 361 table.getTableName(), 362 result.getContinuousBackupsDescription().getPointInTimeRecoveryDescription().getPointInTimeRecoveryStatus()); 363 } catch (Exception e) { 364 Loggers.MAIN_LOG.error("[DS0145] DynamoDB store: Couldn't obtain continuous backup status for table {}: {}", table.getTableName(), e.getMessage()); 365 } 366 367 if (ContinuousBackupsStatus.DISABLED.equals(contBackupStatus) && config.isEnableContinuousBackups()) { 368 369 try { 370 client.updateContinuousBackups( 371 new UpdateContinuousBackupsRequest() 372 .withTableName(table.getTableName()) 373 .withPointInTimeRecoverySpecification( 374 new PointInTimeRecoverySpecification() 375 .withPointInTimeRecoveryEnabled(true))); 376 377 } catch (Exception e) { 378 String msg = "Couldn't set up continuous backups for table " + table.getTableName() + ": " + e.getMessage(); 379 Loggers.MAIN_LOG.fatal("[DS0104] DynamoDB store: {}", msg); 380 throw new PersistenceException(msg, e); 381 } 382 } 383 384 TimeToLiveStatus currentTTLStatus = null; 385 386 try { 387 // Not available on localhost DynamoDB 388 DescribeTimeToLiveResult result = client.describeTimeToLive(new DescribeTimeToLiveRequest().withTableName(table.getTableName())); 389 currentTTLStatus = TimeToLiveStatus.fromValue(result.getTimeToLiveDescription().getTimeToLiveStatus()); 390 Loggers.MAIN_LOG.info("[DS0161] DynamoDB store: TTL for table {}: status={} attribute={}", 391 table.getTableName(), 392 currentTTLStatus, 393 result.getTimeToLiveDescription().getAttributeName()); 394 } catch (Exception e) { 395 Loggers.MAIN_LOG.error("[DS0162] DynamoDB store: Couldn't obtain TTL status for table {}: {}", table.getTableName(), e.getMessage()); 396 } 397 398 if (TimeToLiveStatus.DISABLED.equals(currentTTLStatus) && config.isEnableTTL() && requestFactory.getItemTransformer().getTTLAttributeName() != null) { 399 try { 400 client.updateTimeToLive( 401 new UpdateTimeToLiveRequest() 402 .withTableName(table.getTableName()) 403 .withTimeToLiveSpecification( 404 new TimeToLiveSpecification() 405 .withAttributeName(requestFactory.getItemTransformer().getTTLAttributeName()) 406 .withEnabled(true))); 407 } catch (Exception e) { 408 String msg = "Couldn't set up TTL for table " + table.getTableName() + ": " + e.getMessage(); 409 Loggers.MAIN_LOG.fatal("[DS0160] DynamoDB store: {}", msg); 410 throw new PersistenceException(msg, e); 411 } 412 } 413 414 reaper = new ExpiredEntryReaper<>(marshalledEntryFactory, table, requestFactory, config.getPurgeLimit(), meters.purgeTimer); 415 416 Loggers.MAIN_LOG.info("[DS0104] Started DynamoDB external store connector for cache {} with table {}", getCacheName(), table.getTableName()); 417 } 418 419 420 @Override 421 public void stop() { 422 423 super.stop(); 424 425 if (client != null) { 426 client.shutdown(); 427 } 428 429 Loggers.MAIN_LOG.info("[DS0105] Stopped DynamoDB store connector for cache {}", getCacheName()); 430 } 431 432 433 @Override 434 public boolean contains(final Object key) { 435 436 // This method will be invoked by the PersistenceManager to determine if the loader contains the specified key. 437 // The implementation should be as fast as possible, e.g. it should strive to transfer the least amount of data possible 438 // from the external storage to perform the check. Also, if possible, make sure the field is indexed on the external storage 439 // so that its existence can be determined as quickly as possible. 440 // 441 // Note that keys will be in the cache's native format, which means that if the cache is being used by a remoting protocol 442 // such as HotRod or REST and compatibility mode has not been enabled, then they will be encoded in a byte[]. 443 444 Loggers.DYNAMODB_LOG.trace("[DS0106] DynamoDB store: Checking {} cache key {}", getCacheName(), key); 445 446 Timer.Context timerCtx = meters.getTimer.time(); 447 448 try { 449 return table.getItem(requestFactory.resolveGetItemSpec(key)) != null; 450 451 } catch (Exception e) { 452 Loggers.DYNAMODB_LOG.error("[DS0107] {}: {}", e.getMessage(), e); 453 throw new PersistenceException(e.getMessage(), e); 454 } finally { 455 timerCtx.stop(); 456 } 457 } 458 459 460 @Override 461 public MarshalledEntry<K,V> load(final Object key) { 462 463 // Fetches an entry from the storage using the specified key. The CacheLoader should retrieve from the external storage all 464 // of the data that is needed to reconstruct the entry in memory, i.e. the value and optionally the metadata. This method 465 // needs to return a MarshalledEntry which can be constructed as follows: 466 // 467 // ctx.getMarshalledEntryFactory().new MarshalledEntry(key, value, metadata); 468 // 469 // If the entry does not exist or has expired, this method should return null. 470 // If an error occurs while retrieving data from the external storage, this method should throw a PersistenceException 471 // 472 // 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 473 // such as HotRod or REST and compatibility mode has not been enabled, then they will be encoded in a byte[]. 474 // 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 475 // classes and the marshaller used to encode them. 476 477 Loggers.DYNAMODB_LOG.trace("[DS0108] DynamoDB store: Loading {} cache entry with key {}", getCacheName(), key); 478 479 Item item; 480 481 Timer.Context timerCtx = meters.getTimer.time(); 482 483 GetItemSpec getItemSpec = requestFactory.resolveGetItemSpec(key); 484 485 try { 486 item = table.getItem(getItemSpec); 487 488 } catch (Exception e) { 489 Loggers.DYNAMODB_LOG.error("[DS0109] {}, {}", e.getMessage(), e); 490 throw new PersistenceException(e.getMessage(), e); 491 } finally { 492 timerCtx.stop(); 493 } 494 495 if (item == null) { 496 // Not found 497 Loggers.DYNAMODB_LOG.trace("[DS0110] DynamoDB store: Item with key {} not found", key); 498 return null; 499 } 500 501 if (Loggers.DYNAMODB_LOG.isTraceEnabled()) { 502 Loggers.DYNAMODB_LOG.trace("[DS0111] DynamoDB store: Retrieved item: {}", item); 503 } 504 505 try { 506 item = itemHMAC.verify(item); 507 } catch (InvalidHMACException e) { 508 meters.invalidItemHmacCounter.inc(); 509 Loggers.DYNAMODB_LOG.error("[DS0131] DynamoDB store: Invalid item HMAC in {} for {}", getCacheName(), item.toJSON()); 510 throw new PersistenceException(e.getMessage(), e); 511 } catch (NoSuchAlgorithmException | InvalidKeyException e) { 512 throw new PersistenceException(e.getMessage(), e); 513 } 514 515 // Transform DynamoDB entry to Infinispan entry 516 InfinispanEntry<K,V> infinispanEntry = itemTransformer.toInfinispanEntry(item); 517 518 if (infinispanEntry.isExpired()) { 519 Loggers.DYNAMODB_LOG.trace("[DS0114] DynamoDB store: Item with key {} expired", key); 520 return null; 521 } 522 523 return marshalledEntryFactory.newMarshalledEntry( 524 infinispanEntry.getKey(), 525 infinispanEntry.getValue(), 526 infinispanEntry.getMetadata()); 527 } 528 529 530 @Override 531 public boolean delete(final Object key) { 532 533 // The CacheWriter should remove from the external storage the entry identified by the specified key. 534 // Note that keys will be in the cache's native format, which means that if the cache is being used by a remoting protocol 535 // such as HotRod or REST and compatibility mode has not been enabled, then they will be encoded in a byte[]. 536 537 Loggers.DYNAMODB_LOG.trace("[DS0112] DynamoDB store: Deleting {} cache entry with key {}", getCacheName(), key); 538 539 final boolean deleted; 540 541 Timer.Context timerCtx = meters.deleteTimer.time(); 542 543 try { 544 deleted = table.deleteItem(requestFactory.resolveDeleteItemSpec(key)).getItem() != null; 545 546 } catch (Exception e) { 547 Loggers.DYNAMODB_LOG.error("[DS0113] {}, {}", e.getMessage(), e); 548 throw new PersistenceException(e.getMessage(), e); 549 } finally { 550 timerCtx.stop(); 551 } 552 553 if (deleted) { 554 Loggers.DYNAMODB_LOG.trace("[DS0116] DynamoDB store: Deleted item with key {}", key); 555 } 556 557 return deleted; 558 } 559 560 561 @Override 562 public void write(final MarshalledEntry<? extends K, ? extends V> marshalledEntry) { 563 564 // The CacheWriter should write the specified entry to the external storage. 565 // 566 // The PersistenceManager uses MarshalledEntry as the default format so that CacheWriters can efficiently store data coming 567 // from a remote node, thus avoiding any additional transformation steps. 568 // 569 // 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 570 // such as HotRod or REST and compatibility mode has not been enabled, then they will be encoded in a byte[]. 571 572 Loggers.DYNAMODB_LOG.trace("[DS0115] DynamoDB store: Writing {} cache entry {}", getCacheName(), marshalledEntry); 573 574 Timer.Context timerCtx = meters.putTimer.time(); 575 576 try { 577 Item item = requestFactory.resolveItem( 578 new InfinispanEntry<>( 579 marshalledEntry.getKey(), 580 marshalledEntry.getValue(), 581 marshalledEntry.getMetadata())); 582 583 item = itemHMAC.apply(item); 584 585 table.putItem(item); 586 587 } catch (Exception e) { 588 Loggers.DYNAMODB_LOG.error("[DS0117] {}: {}", e.getMessage(), e); 589 throw new PersistenceException(e.getMessage(), e); 590 } finally { 591 timerCtx.stop(); 592 } 593 } 594 595 @Override 596 public Publisher<MarshalledEntry<K, V>> publishEntries(Predicate<? super K> filter, boolean fetchValue, boolean fetchMetadata) { 597 598 Loggers.DYNAMODB_LOG.trace("[DS0118] DynamoDB store: Processing key filter for {} cache: fetchValue={} fetchMetadata={}", 599 getCacheName(), fetchValue, fetchMetadata); 600 601 final Instant now = Instant.now(); 602 603 return Flowable.using(meters.processTimer::time, 604 ignore -> Flowable.fromIterable(requestFactory.getAllItems(table)) 605 .map(itemTransformer::toInfinispanEntry) 606 .filter(infinispanEntry -> filter == null || filter.test(infinispanEntry.getKey())) 607 .filter(infinispanEntry -> ! infinispanEntry.isExpired(now)) 608 .map(infinispanEntry -> marshalledEntryFactory.newMarshalledEntry( 609 infinispanEntry.getKey(), 610 infinispanEntry.getValue(), 611 infinispanEntry.getMetadata())) 612 .doOnError(e -> Loggers.DYNAMODB_LOG.error("[DS0119] {}: {}", e.getMessage(), e)), 613 Timer.Context::stop); 614 } 615 616 @Override 617 public int size() { 618 619 // Infinispan code analysis on 8.2 and 9.4.11 shows that this method is never called in practice, and 620 // is not wired to the data / cache container API 621 622 // TODO inaccurate when a range key is applied! 623 624 Loggers.DYNAMODB_LOG.trace("[DS0120] DynamoDB store: Counting {} records", getCacheName()); 625 626 final int count; 627 628 try { 629 count = table.describe().getItemCount().intValue(); 630 631 } catch (Exception e) { 632 Loggers.DYNAMODB_LOG.error("[DS0121] {}: {}", e.getMessage(), e); 633 throw new PersistenceException(e.getMessage(), e); 634 } 635 636 Loggers.DYNAMODB_LOG.trace("[DS0122] DynamoDB store: Reported approximately {} {} items", count, getCacheName()); 637 638 return count; 639 } 640 641 642 @Override 643 public void clear() { 644 645 Loggers.DYNAMODB_LOG.trace("[DS0123] DynamoDB store: Clearing {} items", getCacheName()); 646 647 if (requestFactory.getRangeKeyResolvedName() != null) { 648 throw new PersistenceException("DynamoDB clear operation not supported with applied range key"); 649 } 650 651 try { 652 DeleteTableResult result = table.delete(); 653 654 int numDeleted = result.getTableDescription().getItemCount().intValue(); 655 656 Loggers.DYNAMODB_LOG.info("[DS0125] DynamoDB store: Cleared {} {} items", numDeleted, table.getTableName()); 657 658 table.waitForDelete(); 659 660 client.createTable(requestFactory.resolveCreateTableRequest()); 661 662 table.waitForActive(); 663 664 } catch (Exception e) { 665 Loggers.DYNAMODB_LOG.error("[DS0124] {}: {}", e.getMessage(), e); 666 throw new PersistenceException(e.getMessage(), e); 667 } 668 } 669 670 671 @Override 672 public void purge(final Executor executor, final PurgeListener<? super K> purgeListener) { 673 674 Loggers.DYNAMODB_LOG.trace("[DS0126] DynamoDB store: Purging {} cache entries", getCacheName()); 675 676 try { 677 executor.execute(() -> reaper.purge(purgeListener)); 678 679 } catch (Exception e) { 680 Loggers.DYNAMODB_LOG.error("[DS0127] {}: {}", e.getMessage(), e); 681 throw new PersistenceException("Purge exception: " + e.getMessage(), e); 682 } 683 } 684 685 686 @Override 687 public void purge(final Executor executor, final ExpirationPurgeListener<K,V> purgeListener) { 688 689 Loggers.DYNAMODB_LOG.trace("[DS0150] DynamoDB store: Purging {} cache entries", getCacheName()); 690 691 try { 692 executor.execute(() -> reaper.purgeExtended(purgeListener)); 693 694 } catch (Exception e) { 695 Loggers.DYNAMODB_LOG.error("[DS0151] {}: {}", e.getMessage(), e); 696 throw new PersistenceException("Purge exception: " + e.getMessage(), e); 697 } 698 } 699}