Class JsonStore
- All Implemented Interfaces:
DataStore
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
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionprivate static final classComposite operator-cache key: the requesting identity (plugin name on the plugin path, canonical data-folder path on the externalFilepath) plus the entity class. -
Field Summary
FieldsModifier and TypeFieldDescriptionprivate final Map<JsonStore.OperatorKey, Cached> Operator cache, keyed by (requesting identity, entity class).private final JsonLockTableThe 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.private static booleanprivate final Stringprivate final Map<String, JsonTransactionManager> Per-identityJsonTransactionManager, one per requesting identity -- the same identitydataOperatorMapalready keys by -- shared by everySimpleJsonDataOperatorthis store hands out for that identity (02-05, D-03). -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate static StringcanonicalPath(File dataFolder) private voidvoidSave all possible caches and destroy all data operation classes.private static voidensureTableAnnotation(Class<?> dataEntity) private static StringexternalLocation(File dataFolder, Class<?> dataEntity) The external-plugin storage location shape:<plugin's own data folder>/data/<@Table value>.private static PathexternalRoot(File dataFolder) The identity root of an external plugin:<plugin's own data folder>/data.(package private) voidThe periodic flush: runs one checkpoint per identity (JsonIdentity.checkpoint()), writing every entry of that plugin changed since its last checkpoint through one checkpoint record, thengc()on each of the identity's operators.(package private) intGet the number of registered data operators.<T extends BaseDataEntity<String>>
DataOperator<T> getOperator(UltiToolsPlugin plugin, Class<T> dataEntity) Get data operation entity class.<T extends BaseDataEntity<String>>
DataOperator<T> getOperator(File dataFolder, Class<T> dataEntity) Get data operator for an external plugin, scoped to the plugin's own data folder.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@Tableannotation have already been established by whichever one of them was called.<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 ownscope.owns(...)check has already passed.Get the type of data storage.private static StringidentityOf(DataScope scope) Derives the same identitygetOperator(UltiToolsPlugin, Class)andgetOperator(File, Class)each already resolve forJsonStore.OperatorKey, from aDataScopeinstead of a raw plugin orFile.private voidInitialize the flush scheduler.private StringinternalLocation(String identity, Class<?> dataEntity) The internal-plugin storage location shape:<store location>/<plugin name>/<@Table value>.private PathinternalRoot(String identity) The identity root of an internal plugin:<store location>/<plugin name>.private static booleanisInternalScope(DataScope scope) Whetherscopeis an internal plugin's own scope -- itsdataFolderis exactly the core framework's own data folder (DataScope.forPlugin's construction).(package private) voidReset the scheduler state.(package private) voidsetLockWaitBoundMillis(long millis) Test seam: sets the default upper bound of one table-lock wait for every operator of this store (5 seconds unless set).transactionManagerFor(DataScope scope) Resolves (creating on first use) theJsonTransactionManagerforscope, giving the JSON backend a realTransactionManagerso@Transactionalis no longer refused ondatasource.type: json(02-05, D-03).private JsonTransactionManagertransactionManagerFor(String identity, Path root) Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods inherited from interface com.ultikits.ultitools.interfaces.DataStore
checkOwnership, checkOwnership, getDataSource, getOperator
-
Field Details
-
dataOperatorMap
Operator cache, keyed by (requesting identity, entity class). Not static -- see class javadoc. -
schedulerInitialized
private static volatile boolean schedulerInitialized -
storeLocation
-
transactionManagers
Per-identityJsonTransactionManager, one per requesting identity -- the same identitydataOperatorMapalready keys by -- shared by everySimpleJsonDataOperatorthis store hands out for that identity (02-05, D-03). Each manager holds the identity'sJsonIdentity: its journal, commit gate and operators. Not static, same reasoning asdataOperatorMap. -
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
-
-
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
JsonStoreinstance in a JVM ever starts the periodic flush task (guarded by thestaticschedulerInitializedflag, matching pre-existing behavior); in production there is exactly oneJsonStoreinstance, 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, thengc()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 theBukkitRunnablefor 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:DataStoreGet data operation entity class.- Specified by:
getOperatorin interfaceDataStore- Type Parameters:
T- Must inheritBaseDataEntity- Parameters:
plugin- PlugindataEntity- 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:DataStoreGet data operator for an external plugin, scoped to the plugin's own data folder. Refuses outright (D-18) whendataFolderresolves to a registered external plugin's scope that does not owndataEntity, or whendataFoldermatches no registered scope at all -- an unresolvable folder fails closed rather than proceeding unchecked (D-15). ADataStorethat overrides this method directly (as every backend shipped by this framework does) does not inherit this check; see the@deprecatednote.- Specified by:
getOperatorin interfaceDataStore- 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 ownscope.owns(...)check has already passed. Branches internal/external exactly likeidentityOf(DataScope)already does fortransactionManagerFor(DataScope)-- an internal scope resolves tointernalLocation(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 toexternalLocation(File, Class).- Specified by:
getOperatorUncheckedin interfaceDataStore- Type Parameters:
T- Must inheritBaseDataEntity- Parameters:
scope- the requesting scope, already verified to owndataEntitydataEntity- 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@Tableannotation have already been established by whichever one of them was called. Single-sourced so the operator cache (keyed by (identity, entity)) and thetransactionManagerFor(...)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
-
internalRoot
The identity root of an internal plugin:<store location>/<plugin name>. -
externalRoot
The identity root of an external plugin:<plugin's own data folder>/data. -
internalLocation
The internal-plugin storage location shape:<store location>/<plugin name>/<@Table value>. Shared bygetOperator(UltiToolsPlugin, Class)andgetOperatorUnchecked(DataScope, Class)'s internal branch. -
externalLocation
The external-plugin storage location shape:<plugin's own data folder>/data/<@Table value>. Shared bygetOperator(File, Class)andgetOperatorUnchecked(DataScope, Class)'s external branch. -
isInternalScope
Whetherscopeis an internal plugin's own scope -- itsdataFolderis exactly the core framework's own data folder (DataScope.forPlugin's construction). The same disambiguationidentityOf(DataScope)already uses fortransactionManagerFor(DataScope)(02-05), extracted sogetOperatorUnchecked(DataScope, Class)can also pick the matching location-construction formula, not just the matching identity string. -
transactionManagerFor
Resolves (creating on first use) theJsonTransactionManagerforscope, giving the JSON backend a realTransactionManagerso@Transactionalis no longer refused ondatasource.type: json(02-05, D-03). Called byPluginManager.wireAop's JSON branch, a different package -- public for that reason, even though it is not part of the officially-extensibleDataStorecontract; the staging, table locking and journaling it drives stay package-private in this package, onSimpleJsonDataOperator,JsonTransaction,JsonIdentityand this store's lock table.Shares one instance with every
SimpleJsonDataOperatorgetOperator(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.lang.Class<T>)hands out for the same identity, so a single@Transactionalmethod'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
-
identityOf
Derives the same identitygetOperator(UltiToolsPlugin, Class)andgetOperator(File, Class)each already resolve forJsonStore.OperatorKey, from aDataScopeinstead of a raw plugin orFile.DataScopecarries both a plugin name and a data folder but no explicit internal-vs-external discriminator, so this disambiguates the same wayDataScope.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
-
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, asflushAllCaches()does.- Specified by:
destroyAllOperatorsin interfaceDataStore
-
getStoreType
Description copied from interface:DataStoreGet the type of data storage.- Specified by:
getStoreTypein interfaceDataStore- Returns:
- Data storage type
-