|
1 | 1 | package net.staticstudios.data; |
2 | 2 |
|
3 | 3 | import com.google.common.base.Preconditions; |
| 4 | +import com.google.common.base.Predicate; |
4 | 5 | import com.google.common.collect.ArrayListMultimap; |
5 | 6 | import com.google.common.collect.HashMultimap; |
6 | 7 | import com.google.common.collect.Multimap; |
|
13 | 14 | import net.staticstudios.data.data.UniqueData; |
14 | 15 | import net.staticstudios.data.data.collection.PersistentManyToManyCollection; |
15 | 16 | import net.staticstudios.data.data.collection.PersistentUniqueDataCollection; |
16 | | -import net.staticstudios.data.data.collection.PersistentValueCollection; |
17 | 17 | import net.staticstudios.data.data.collection.SimplePersistentCollection; |
18 | 18 | import net.staticstudios.data.data.value.Value; |
19 | 19 | import net.staticstudios.data.data.value.persistent.InitialPersistentValue; |
|
51 | 51 | import java.util.*; |
52 | 52 | import java.util.concurrent.ConcurrentHashMap; |
53 | 53 | import java.util.concurrent.CopyOnWriteArrayList; |
| 54 | +import java.util.stream.Collectors; |
54 | 55 |
|
55 | 56 | public class DataManager extends SQLLogger { |
56 | 57 | private static final Object NULL_MARKER = new Object(); |
@@ -165,10 +166,22 @@ public Collection<UniqueData> getDummyUniqueData(String schemaTable) { |
165 | 166 | return dummyUniqueDataMap.get(schemaTable); |
166 | 167 | } |
167 | 168 |
|
| 169 | + public UniqueData getDummyInstance(Class<?> clazz) { |
| 170 | + return dummyInstances.get(clazz); |
| 171 | + } |
| 172 | + |
168 | 173 | public Collection<SimplePersistentCollection<?>> getDummyPersistentCollections(String schemaTable) { |
169 | 174 | return dummySimplePersistentCollectionMap.get(schemaTable); |
170 | 175 | } |
171 | 176 |
|
| 177 | + public Collection<PersistentManyToManyCollection<?>> getDummyPersistentManyToManyCollection(String schemaTable) { |
| 178 | + return dummyPersistentManyToManyCollectionMap.get(schemaTable); |
| 179 | + } |
| 180 | + |
| 181 | + public Collection<PersistentManyToManyCollection<?>> getAllDummyPersistentManyToManyCollections() { |
| 182 | + return new HashSet<>(dummyPersistentManyToManyCollectionMap.values()); |
| 183 | + } |
| 184 | + |
172 | 185 | @Blocking |
173 | 186 | public <T extends UniqueData> List<T> loadAll(Class<T> clazz) { |
174 | 187 | logger.debug("Registering: {}", clazz.getName()); |
@@ -388,6 +401,7 @@ public <T extends UniqueData> List<T> loadAll(Class<T> clazz) { |
388 | 401 |
|
389 | 402 | private void insertIntoCache(InsertContext context) { |
390 | 403 | addUniqueData(context.holder()); |
| 404 | + //todo: REFACTOR: similar to deletions, delegate this to the managers |
391 | 405 |
|
392 | 406 | for (InitialPersistentValue data : context.initialPersistentValues().values()) { |
393 | 407 | UniqueData pvHolder = data.getValue().getHolder().getRootHolder(); |
@@ -433,81 +447,157 @@ private void insertIntoCache(InsertContext context) { |
433 | 447 | } |
434 | 448 |
|
435 | 449 | private void insertIntoDataSource(Connection connection, Jedis jedis, InsertContext context) throws SQLException { |
436 | | - persistentValueManager.insertInDatabase(connection, context.holder(), new ArrayList<>(context.initialPersistentValues().values())); |
437 | | - cachedValueManager.setInRedis(jedis, new ArrayList<>(context.initialCachedValues().values())); |
438 | | - } |
439 | | - |
440 | | - public <T extends UniqueData> void delete(T holder) { |
441 | | - List<Data<?>> dataList = new ArrayList<>(); |
| 450 | + if (context.initialPersistentValues().isEmpty() && context.initialCachedValues().isEmpty()) { |
| 451 | + String sql = "INSERT INTO " + context.holder().getSchema() + "." + context.holder().getTable() + " (" + context.holder().getIdentifier().getColumn() + ") VALUES (?)"; |
| 452 | + logSQL(sql); |
442 | 453 |
|
443 | | - for (Field field : ReflectionUtils.getFields(holder.getClass())) { |
444 | | - field.setAccessible(true); |
445 | | - |
446 | | - if (Data.class.isAssignableFrom(field.getType())) { |
447 | | - try { |
448 | | - Data<?> data = (Data<?>) field.get(holder); |
449 | | - dataList.add(data); |
450 | | - } catch (IllegalAccessException e) { |
451 | | - throw new RuntimeException(e); |
452 | | - } |
| 454 | + try (PreparedStatement statement = connection.prepareStatement(sql)) { |
| 455 | + statement.setObject(1, context.holder().getId()); |
| 456 | + statement.executeUpdate(); |
453 | 457 | } |
| 458 | + return; |
454 | 459 | } |
| 460 | + persistentValueManager.insertInDatabase(connection, context.holder(), new ArrayList<>(context.initialPersistentValues().values())); |
| 461 | + cachedValueManager.setInRedis(jedis, new ArrayList<>(context.initialCachedValues().values())); |
| 462 | + } |
455 | 463 |
|
456 | | - deleteFromCache(holder, dataList); |
457 | | - removeUniqueData(holder.getClass(), holder.getId()); |
458 | | - //todo: remove add, remove, and update handlers |
459 | | - //todo: handle foreign PVs |
460 | | - //todo: handle CVs |
461 | | - //todo: handle PVCs |
462 | | - //todo: handle PUDCs |
463 | | - //todo: handle PMTMCs |
| 464 | + public void delete(UniqueData holder) { |
| 465 | + DeleteContext context = buildDeleteContext(holder); |
| 466 | + logger.trace("Deleting: {}", context); |
| 467 | + deleteFromCache(context); |
| 468 | + //todo: i really dislike that update handlers are called when things are deleted. revisit this |
464 | 469 |
|
465 | 470 | ThreadUtils.submit(() -> { |
466 | 471 | try (Connection connection = getConnection(); |
467 | 472 | Jedis jedis = jedisProvider.getJedis() |
468 | 473 | ) { |
469 | | - deleteFromDataSource(connection, jedis, holder); |
| 474 | + deleteFromDataSource(connection, jedis, context); |
470 | 475 | } catch (SQLException e) { |
471 | 476 | logger.error("Error deleting data", e); |
472 | 477 | throw new RuntimeException(e); |
473 | 478 | } |
474 | 479 | }); |
475 | 480 | } |
476 | 481 |
|
477 | | - private void deleteFromCache(UniqueData holder, List<Data<?>> dataList) { |
478 | | - for (Data<?> data : dataList) { |
479 | | - if (data instanceof PersistentValue<?> value) { |
480 | | - persistentValueManager.uncache(value); |
481 | | - |
482 | | - //Uncache the id column as well |
483 | | - persistentValueManager.uncache( |
484 | | - value.getSchema(), |
485 | | - value.getTable(), |
486 | | - value.getIdColumn(), |
487 | | - holder.getId(), |
488 | | - value.getIdColumn() |
489 | | - ); |
490 | | - } else if (data instanceof CachedValue<?> value) { |
491 | | - cache.remove(value.getKey()); |
492 | | - } else if (data instanceof PersistentValueCollection<?> collection) { |
493 | | - persistentCollectionManager.removeEntriesFromCache(collection, persistentCollectionManager.getCollectionEntries(collection)); |
494 | | - } else if (data instanceof PersistentUniqueDataCollection<?> collection) { |
495 | | - persistentCollectionManager.removeEntriesFromInternalMap(collection, persistentCollectionManager.getCollectionEntries(collection)); |
| 482 | + public void deleteSync(UniqueData holder) { |
| 483 | + DeleteContext context = buildDeleteContext(holder); |
| 484 | + logger.trace("Deleting: {}", context); |
| 485 | + deleteFromCache(context); |
| 486 | + |
| 487 | + try (Connection connection = getConnection(); |
| 488 | + Jedis jedis = jedisProvider.getJedis() |
| 489 | + ) { |
| 490 | + deleteFromDataSource(connection, jedis, context); |
| 491 | + } catch (SQLException e) { |
| 492 | + logger.error("Error deleting data", e); |
| 493 | + throw new RuntimeException(e); |
| 494 | + } |
| 495 | + } |
| 496 | + |
| 497 | + private DeleteContext buildDeleteContext(UniqueData holder) { |
| 498 | + Set<Data<?>> toDelete = new HashSet<>(); |
| 499 | + Set<UniqueData> holders = new HashSet<>(); |
| 500 | + extractDataToDelete(holder, holders, toDelete); |
| 501 | + Map<DataKey, Object> oldValues = new HashMap<>(); |
| 502 | + for (Data<?> data : toDelete) { |
| 503 | + if (data instanceof PersistentValue<?> || data instanceof CachedValue<?>) { |
| 504 | + try { |
| 505 | + Object value = get(data.getKey()); |
| 506 | + oldValues.put(data.getKey(), value == NULL_MARKER ? null : value); |
| 507 | + } catch (DataDoesNotExistException e) { |
| 508 | + // This is fine, it just means the value was null |
| 509 | + } |
496 | 510 | } |
497 | 511 | } |
498 | 512 |
|
| 513 | + return new DeleteContext(holders, toDelete, oldValues); |
499 | 514 | } |
500 | 515 |
|
501 | | - private void deleteFromDataSource(Connection connection, Jedis jedis, UniqueData holder) throws SQLException { |
502 | | - String sql = "DELETE FROM " + holder.getSchema() + "." + holder.getTable() + " WHERE " + holder.getIdentifier().getColumn() + " = ?"; |
503 | | - logSQL(sql); |
| 516 | + private void extractDataToDelete(UniqueData holder, Set<UniqueData> holders, Set<Data<?>> toDelete) { |
| 517 | + if (holders.contains(holder)) { |
| 518 | + return; |
| 519 | + } |
| 520 | + holders.add(holder); |
504 | 521 |
|
505 | | - try (PreparedStatement statement = connection.prepareStatement(sql)) { |
506 | | - statement.setObject(1, holder.getId()); |
507 | | - statement.execute(); |
| 522 | + //Add the root holder's id column to the list of things to delete, just in case the holder is empty |
| 523 | + toDelete.add(PersistentValue.of(holder.getRootHolder(), UUID.class, holder.getRootHolder().getIdentifier().getColumn())); |
| 524 | + |
| 525 | + for (Field field : ReflectionUtils.getFields(holder.getClass())) { |
| 526 | + field.setAccessible(true); |
| 527 | + |
| 528 | + if (Data.class.isAssignableFrom(field.getType())) { |
| 529 | + try { |
| 530 | + Data<?> data = (Data<?>) field.get(holder); |
| 531 | + |
| 532 | + //Always delete the backing value since it's in the same table as the holder |
| 533 | + if (data instanceof Reference<?> reference) { |
| 534 | + toDelete.add(reference.getBackingValue()); |
| 535 | + } |
| 536 | + |
| 537 | + if (data.getDeletionStrategy() == DeletionStrategy.NO_ACTION) { |
| 538 | + continue; |
| 539 | + } |
| 540 | + |
| 541 | + if (data instanceof Reference<?> reference) { |
| 542 | + UUID id = reference.getForeignId(); |
| 543 | + if (id != null) { |
| 544 | + UniqueData foreignData = reference.get(); |
| 545 | + if (foreignData != null) { |
| 546 | + extractDataToDelete(foreignData, holders, toDelete); |
| 547 | + } |
| 548 | + } |
| 549 | + } |
| 550 | + |
| 551 | + if (data instanceof PersistentUniqueDataCollection<?> collection) { |
| 552 | + if (collection.getDeletionStrategy() == DeletionStrategy.CASCADE) { |
| 553 | + for (UniqueData dataInCollection : collection) { |
| 554 | + extractDataToDelete(dataInCollection, holders, toDelete); |
| 555 | + } |
| 556 | + } |
| 557 | + } |
| 558 | + |
| 559 | + if (data instanceof PersistentManyToManyCollection<?> collection) { |
| 560 | + if (collection.getDeletionStrategy() == DeletionStrategy.CASCADE) { |
| 561 | + for (UniqueData dataInCollection : collection) { |
| 562 | + extractDataToDelete(dataInCollection, holders, toDelete); |
| 563 | + } |
| 564 | + } |
| 565 | + } |
| 566 | + |
| 567 | + toDelete.add(data); |
| 568 | + } catch (IllegalAccessException e) { |
| 569 | + throw new RuntimeException(e); |
| 570 | + } |
| 571 | + } |
| 572 | + } |
| 573 | + } |
| 574 | + |
| 575 | + private void deleteFromCache(DeleteContext context) { |
| 576 | + persistentCollectionManager.deleteFromCache(context); |
| 577 | + persistentValueManager.deleteFromCache(context); |
| 578 | + cachedValueManager.deleteFromCache(context); |
| 579 | + |
| 580 | + for (UniqueData holder : context.holders()) { |
| 581 | + removeUniqueData(holder.getClass(), holder.getId()); |
508 | 582 | } |
509 | 583 | } |
510 | 584 |
|
| 585 | + @Blocking |
| 586 | + private void deleteFromDataSource(Connection connection, Jedis jedis, DeleteContext context) throws SQLException { |
| 587 | + for (UniqueData holder : context.holders()) { |
| 588 | + String sql = "DELETE FROM " + holder.getSchema() + "." + holder.getTable() + " WHERE " + holder.getIdentifier().getColumn() + " = ?"; |
| 589 | + logSQL(sql); |
| 590 | + |
| 591 | + try (PreparedStatement statement = connection.prepareStatement(sql)) { |
| 592 | + statement.setObject(1, holder.getId()); |
| 593 | + statement.executeUpdate(); |
| 594 | + } |
| 595 | + } |
| 596 | + persistentValueManager.deleteFromDatabase(connection, context); |
| 597 | + persistentCollectionManager.deleteFromDatabase(connection, context); |
| 598 | + cachedValueManager.deleteFromRedis(jedis, context); |
| 599 | + } |
| 600 | + |
511 | 601 | public <T extends UniqueData> List<T> getAll(Class<T> clazz) { |
512 | 602 | return uniqueDataIds.get(clazz).stream().map(id -> get(clazz, id)).toList(); |
513 | 603 | } |
@@ -567,7 +657,13 @@ private List<Data<?>> extractDataDependencies(Class<? extends UniqueData> clazz) |
567 | 657 | return dependencies; |
568 | 658 | } |
569 | 659 |
|
| 660 | + public synchronized void removeFromCacheIf(Predicate<DataKey> predicate) { |
| 661 | + Set<DataKey> keysToRemove = cache.keySet().stream().filter(predicate).collect(Collectors.toSet()); |
| 662 | + keysToRemove.forEach(cache::remove); |
| 663 | + } |
| 664 | + |
570 | 665 | public void dump() { |
| 666 | + logger.debug("Dumping cache:"); |
571 | 667 | for (Map.Entry<DataKey, CacheEntry> entry : cache.entrySet()) { |
572 | 668 | logger.debug("{} -> {}", entry.getKey(), entry.getValue().value()); |
573 | 669 | } |
@@ -691,6 +787,10 @@ public <T> void cache(DataKey key, Class<?> valueDataType, T value, Instant inst |
691 | 787 | } |
692 | 788 | } |
693 | 789 |
|
| 790 | + public int getCacheSize() { |
| 791 | + return cache.size(); |
| 792 | + } |
| 793 | + |
694 | 794 | public void uncache(DataKey key) { |
695 | 795 | Collection<ValueUpdateHandler<?>> updateHandlers = valueUpdateHandlers.get(key); |
696 | 796 | CacheEntry existing = cache.get(key); |
|
0 commit comments