java.lang.Object
com.ultikits.ultitools.interfaces.impl.data.json.JsonStore
All Implemented Interfaces:
DataStore

public class JsonStore extends Object implements DataStore
Json Data store.

The operator cache is keyed by (requesting identity, entity class), not by entity class alone -- two callers resolving to two different identities (two plugins on the plugin path, or two data folders on the external File path) never share a DataOperator instance (SILENT-04). Not static: DataStoreManager registers exactly one JsonStore instance, and instance scoping is what stops the cache from outliving a /reload.

Version:
1.0.0
Author:
wisdomme
  • Field Details

    • dataOperatorMap

      private final Map<JsonStore.OperatorKey,Cached> dataOperatorMap
      Operator cache, keyed by (requesting identity, entity class). Not static -- see class javadoc.
    • schedulerInitialized

      private static volatile boolean schedulerInitialized
    • storeLocation

      private final String storeLocation
    • transactionManagers

      private final Map<String,JsonTransactionManager> transactionManagers
      Per-identity JsonTransactionManager, one per requesting identity -- the same identity dataOperatorMap already keys by -- shared by every SimpleJsonDataOperator this store hands out for that identity (02-05, D-03). Each manager holds the identity's JsonIdentity: its journal, commit gate and operators. Not static, same reasoning as dataOperatorMap.
    • lockTable

      private final JsonLockTable lockTable
      The table write locks of every operator this store hands out, across every identity: one lock table, so a thread waiting for one plugin's table while holding another's is visible to the same wait bookkeeping.
  • Constructor Details

    • JsonStore

      public JsonStore(String storeLocation)
  • Method Details

    • initScheduler

      private void initScheduler()
      Initialize the flush scheduler. Called lazily when first JsonStore is created. This avoids static initializer issues in test environments.

      Only the first JsonStore instance in a JVM ever starts the periodic flush task (guarded by the static schedulerInitialized flag, matching pre-existing behavior); in production there is exactly one JsonStore instance, so this flushes that instance's own cache, not a shared static one.

    • flushAllCaches

      void flushAllCaches()
      The periodic flush: runs one checkpoint per identity (JsonIdentity.checkpoint()), writing every entry of that plugin changed since its last checkpoint through one checkpoint record, then gc() on each of the identity's operators. An identity whose checkpoint fails keeps its changed entries pending and does not stop the others; the first failure is rethrown afterwards with the rest suppressed. Extracted from the BukkitRunnable for testability.
    • checkpointEveryIdentity

      private void checkpointEveryIdentity()
    • getDataOperatorCount

      int getDataOperatorCount()
      Get the number of registered data operators. Used for testing.
    • setLockWaitBoundMillis

      void setLockWaitBoundMillis(long millis)
      Test seam: sets the default upper bound of one table-lock wait for every operator of this store (5 seconds unless set).
      Parameters:
      millis - the bound in milliseconds
    • resetSchedulerState

      void resetSchedulerState()
      Reset the scheduler state. Used for testing.
    • getOperator

      public <T extends BaseDataEntity<String>> DataOperator<T> getOperator(UltiToolsPlugin plugin, Class<T> dataEntity)
      Description copied from interface: DataStore
      Get data operation entity class.
      Specified by:
      getOperator in interface DataStore
      Type Parameters:
      T - Must inherit BaseDataEntity
      Parameters:
      plugin - Plugin
      dataEntity - Data entity class
      Returns:
      Data operation entity
    • getOperator

      public <T extends BaseDataEntity<String>> DataOperator<T> getOperator(File dataFolder, Class<T> dataEntity)
      Description copied from interface: DataStore
      Get data operator for an external plugin, scoped to the plugin's own data folder. Refuses outright (D-18) when dataFolder resolves to a registered external plugin's scope that does not own dataEntity, or when dataFolder matches no registered scope at all -- an unresolvable folder fails closed rather than proceeding unchecked (D-15). A DataStore that overrides this method directly (as every backend shipped by this framework does) does not inherit this check; see the @deprecated note.
      Specified by:
      getOperator in interface DataStore
      Type Parameters:
      T - entity type
      Parameters:
      dataFolder - the external plugin's data folder (e.g., plugins/MyPlugin/)
      dataEntity - data entity class
      Returns:
      data operator
    • getOperatorUnchecked

      public <T extends BaseDataEntity<String>> DataOperator<T> getOperatorUnchecked(DataScope scope, Class<T> dataEntity)
      Internal, unchecked construction path (CR-01/CR-03, 02-13): DataStore.getOperator(DataScope, Class)'s default body calls this only after its own scope.owns(...) check has already passed. Branches internal/external exactly like identityOf(DataScope) already does for transactionManagerFor(DataScope) -- an internal scope resolves to internalLocation(String, Class) keyed on the plugin's own name (never the framework's shared core folder, the collapse regression 02-07 guarded against and CR-01 found reopened as a bypass), an external scope to externalLocation(File, Class).
      Specified by:
      getOperatorUnchecked in interface DataStore
      Type Parameters:
      T - Must inherit BaseDataEntity
      Parameters:
      scope - the requesting scope, already verified to own dataEntity
      dataEntity - data entity class
      Returns:
      data operator for that entity
    • getOperatorForIdentity

      private <T extends BaseDataEntity<String>> DataOperator<T> getOperatorForIdentity(String identity, Path root, String location, Class<T> dataEntity)
      Shared operator-construction body for all three entry points above, once ownership and the @Table annotation have already been established by whichever one of them was called. Single-sourced so the operator cache (keyed by (identity, entity)) and the transactionManagerFor(...) wiring are each written in exactly one place.

      Recovers the identity's journal before its first operator is constructed, so no table of the identity is ever read before its committed records are applied. A refused recovery (a damaged record, question 8 option A) fails this request and every later one for the same identity, until a request finds the journal recoverable; other identities are unaffected.

    • ensureTableAnnotation

      private static void ensureTableAnnotation(Class<?> dataEntity)
    • internalRoot

      private Path internalRoot(String identity)
      The identity root of an internal plugin: <store location>/<plugin name>.
    • externalRoot

      private static Path externalRoot(File dataFolder)
      The identity root of an external plugin: <plugin's own data folder>/data.
    • internalLocation

      private String internalLocation(String identity, Class<?> dataEntity)
      The internal-plugin storage location shape: <store location>/<plugin name>/<@Table value>. Shared by getOperator(UltiToolsPlugin, Class) and getOperatorUnchecked(DataScope, Class)'s internal branch.
    • externalLocation

      private static String externalLocation(File dataFolder, Class<?> dataEntity)
      The external-plugin storage location shape: <plugin's own data folder>/data/<@Table value>. Shared by getOperator(File, Class) and getOperatorUnchecked(DataScope, Class)'s external branch.
    • isInternalScope

      private static boolean isInternalScope(DataScope scope)
      Whether scope is an internal plugin's own scope -- its dataFolder is exactly the core framework's own data folder (DataScope.forPlugin's construction). The same disambiguation identityOf(DataScope) already uses for transactionManagerFor(DataScope) (02-05), extracted so getOperatorUnchecked(DataScope, Class) can also pick the matching location-construction formula, not just the matching identity string.
    • transactionManagerFor

      public JsonTransactionManager transactionManagerFor(DataScope scope)
      Resolves (creating on first use) the JsonTransactionManager for scope, giving the JSON backend a real TransactionManager so @Transactional is no longer refused on datasource.type: json (02-05, D-03). Called by PluginManager.wireAop's JSON branch, a different package -- public for that reason, even though it is not part of the officially-extensible DataStore contract; the staging, table locking and journaling it drives stay package-private in this package, on SimpleJsonDataOperator, JsonTransaction, JsonIdentity and this store's lock table.

      Shares one instance with every SimpleJsonDataOperator getOperator(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Class<T>) hands out for the same identity, so a single @Transactional method's writes across several of that plugin's entities are governed by the same transaction: staged until the outermost commit and invisible to other threads until then; made durable at commit by one journal record, forced to disk before the commit returns; then applied to every table so other threads see them at one instant. Each table a transaction writes is locked against other writers until it ends. The record is replayed at the next start if the server stops before the next flush, before any of the plugin's tables is read.

      Parameters:
      scope - the identity token minted for the caller
      Returns:
      this store's manager for that caller's identity
    • transactionManagerFor

      private JsonTransactionManager transactionManagerFor(String identity, Path root)
    • identityOf

      private static String identityOf(DataScope scope)
      Derives the same identity getOperator(UltiToolsPlugin, Class) and getOperator(File, Class) each already resolve for JsonStore.OperatorKey, from a DataScope instead of a raw plugin or File.

      DataScope carries both a plugin name and a data folder but no explicit internal-vs-external discriminator, so this disambiguates the same way DataScope.forPlugin(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.util.Set<java.lang.Class<?>>) itself is built: an internal scope's data folder is always the core UltiTools plugin's own folder (shared by every internal plugin), which no external plugin's own folder can equal. When it matches, the plugin-path identity (DataScope.getPluginName()) is used; otherwise the external-path identity (canonicalPath(java.io.File)) is used.

    • canonicalPath

      private static String canonicalPath(File dataFolder)
    • destroyAllOperators

      public void destroyAllOperators()
      Save all possible caches and destroy all data operation classes.

      Runs one final checkpoint per identity, then gc() on its operators, as flushAllCaches() does.

      Specified by:
      destroyAllOperators in interface DataStore
    • getStoreType

      public String getStoreType()
      Description copied from interface: DataStore
      Get the type of data storage.
      Specified by:
      getStoreType in interface DataStore
      Returns:
      Data storage type