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

      @Deprecated(since="6.4.0", forRemoval=true) public <T extends BaseDataEntity<String>> DataOperator<T> getOperator(UltiToolsPlugin plugin, Class<T> dataEntity)
      Deprecated, for removal: This API element is subject to removal in a future version.
      Kept only for binary compatibility with the DataStore overload removed in 6.4.0. Use DataStore.getOperator(DataScope, Class) instead (a module calls UltiToolsPlugin#getDataOperator(Class)).
      Until 6.4.0 this overrode the DataStore overload of the same signature, which 6.4.0 removed. It still checks ownership first and behaves as in 6.3.x.
      Type Parameters:
      T - the entity type
      Parameters:
      plugin - the requesting plugin
      dataEntity - the data entity class
      Returns:
      the data operator
      Scheduled removal version:
      6.5.0
    • getOperator

      @Deprecated(since="6.4.0", forRemoval=true) public <T extends BaseDataEntity<String>> DataOperator<T> getOperator(File dataFolder, Class<T> dataEntity)
      Deprecated, for removal: This API element is subject to removal in a future version.
      Kept only for binary compatibility with the DataStore overload removed in 6.4.0. Use DataStore.getOperator(DataScope, Class) instead (a module calls UltiToolsPlugin#getDataOperator(Class)).
      Until 6.4.0 this overrode the DataStore overload of the same signature, which 6.4.0 removed. It still checks ownership first and behaves as in 6.3.x.
      Type Parameters:
      T - the entity type
      Parameters:
      dataFolder - the external plugin's data folder
      dataEntity - the data entity class
      Returns:
      the data operator
      Scheduled removal version:
      6.5.0
    • 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 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)
      Operator-construction body for getOperatorUnchecked(DataScope, Class), once ownership has already been established. 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: 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. Used by dbPathFor(DataScope).
    • dbPathForFolder

      private static String dbPathForFolder(File dataFolder)
      The external-plugin .db path shape: <plugin's own data folder>/data.db. Used by 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