java.lang.Object
com.ultikits.ultitools.interfaces.impl.data.sqlite.SQLiteDataStore
All Implemented Interfaces:
DataStore

public class SQLiteDataStore extends Object implements DataStore
SQLite-backed DataStore.

A connection pool is keyed by the canonical path of the backing .db file, independent of any single entity class -- two entity classes stored in the same file share exactly one HikariDataSource. The operator cache is keyed by (that same file path, entity class), so two callers resolving to two different files (two plugins, or the plugin path vs. the external File path) never share a DataOperator instance. Neither map is static: DataStoreManager registers exactly one SQLiteDataStore instance, and instance scoping is what stops both maps from outliving a /reload.

Since:
1.0.0
Author:
wisdomme
  • Field Details

    • LOGGER

      private static final Logger LOGGER
    • dataOperatorMap

      private final Map<SQLiteDataStore.OperatorKey,DataOperator<?>> dataOperatorMap
      Operator cache, keyed by (backing .db file canonical path, entity class). Not static -- see class javadoc.
    • dataSourceMap

      private final Map<String,com.zaxxer.hikari.HikariDataSource> dataSourceMap
      Connection pool cache, keyed by the backing .db file's canonical path. One pool per file, regardless of how many entity classes or callers resolve to it. Not static -- see class javadoc.
    • transactionManagerMap

      private final Map<String,JdbcTransactionManager> transactionManagerMap
      JdbcTransactionManager cache, keyed identically to dataSourceMap -- the backing .db file's canonical path (D-02: one JDBC DataSource per file already means one manager per file is "one per plugin container", since each plugin has its own file). Not static -- same reload-safety reasoning as the other two maps.

      This is the single seam through which BOTH transactionManagerFor(DataScope) (called by PluginManager.wireTransactional for the AOP interceptor) and getOperator(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Class<T>) (below, for the operators this store hands module authors) resolve a manager for the same file -- computeIfAbsent guarantees the second caller to touch a given key gets back the exact instance the first one built, never a second, independent one (T-02-TAM-11: two managers over one file would give a @Transactional method calling insertAll two independent transactions, which is worse than no transaction at all because it looks correct).

  • Constructor Details

    • SQLiteDataStore

      public SQLiteDataStore()
  • Method Details

    • 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
    • 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. Resolves scope to the exact same .db path dbPathFor(DataScope) already derives for transactionManagerFor(DataScope) -- internal scopes resolve to dbPathForPlugin(String) keyed on the plugin's own name, external scopes to dbPathForFolder(File) -- so an internal scope never collapses onto the framework's shared core folder the way routing through the public getOperator(File, Class) overload used to (the exact regression 02-07 guarded against and CR-01 found reopened as a security bypass).
      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
    • getOperatorForPath

      private <T extends BaseDataEntity<String>> DataOperator<T> getOperatorForPath(String dbPath, Class<T> dataEntity)
      Shared operator-construction body for all three entry points above, once ownership has already been established by whichever one of them was called. Single-sourced so the pool cache (keyed by backing-file path), the operator cache (keyed by (path, entity)), and the transactionManagerFor(...) wiring are each written in exactly one place.
    • getDataSource

      public DataSource getDataSource(DataScope scope)
      Description copied from interface: DataStore
      Get the JDBC DataSource backing this store for the given scope, needing no entity class up front (a connection pool needs only a JDBC URL).
      Specified by:
      getDataSource in interface DataStore
      Parameters:
      scope - the requesting scope
      Returns:
      the JDBC data source for that scope
    • transactionManagerFor

      public JdbcTransactionManager transactionManagerFor(DataScope scope)
      Resolves the same per-plugin-container JdbcTransactionManager instance getOperator(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Class<T>) wires onto the operators it hands out for scope's own database file -- the seam PluginManager.wireTransactional calls to bind the AOP @Transactional interceptor to it, so a module's data operators and its @Transactional beans share one transaction, not two (T-02-TAM-11).
      Parameters:
      scope - the identity token to resolve the manager for
      Returns:
      the shared manager for that scope's database file
    • transactionManagerForPath

      private JdbcTransactionManager transactionManagerForPath(String dbPath)
      Returns the manager for dbPath, building it on first touch -- the same computeIfAbsent single-construction guarantee poolFor(String) already gives the connection pool for that file.
    • dbPathFor

      private static String dbPathFor(DataScope scope)
      Resolves scope's backing .db file path, matching whichever legacy getOperator overload the same scope would actually route through: the internal ( dbPathForPlugin) shape when 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 JsonStore.identityOf(DataScope) uses, 02-05), the external (dbPathForFolder) shape otherwise.
    • dbPathForPlugin

      private static String dbPathForPlugin(String pluginName)
      The internal-plugin .db path shape: <core data folder>/sqliteDB/<plugin name>.db. Shared by getOperator(UltiToolsPlugin, Class) and dbPathFor(DataScope).
    • dbPathForFolder

      private static String dbPathForFolder(File dataFolder)
      The external-plugin .db path shape: <plugin's own data folder>/data.db. Shared by getOperator(File, Class) and dbPathFor(DataScope).
    • poolFor

      private com.zaxxer.hikari.HikariDataSource poolFor(String dbPath)
      Returns the pool for dbPath, building it on first touch. Map.computeIfAbsent(K, java.util.function.Function<? super K, ? extends V>) makes a concurrent first touch by two callers build exactly one pool, never two.
      Parameters:
      dbPath - canonical path of the backing .db file
      Returns:
      the shared pool for that file
    • ensureExists

      private static void ensureExists(File dataFolder)
    • canonicalPath

      private static String canonicalPath(File dbFile)
    • destroyAllOperators

      public void destroyAllOperators()
      Closes every pool this store holds and empties both caches. Idempotent -- calling it twice closes nothing the second time, since the first call already emptied dataSourceMap. A failure closing one pool is logged and does not stop the rest from being closed.
      Specified by:
      destroyAllOperators in interface DataStore