Class ConfigManager
- Version:
- 1.0.0
- Author:
- wisdomme
-
Field Summary
FieldsModifier and TypeFieldDescriptionprivate final Map<UltiToolsPlugin, Map<String, AbstractConfigEntity>> -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate voidaddConfigEntity(UltiToolsPlugin ultiToolsPlugin, AbstractConfigEntity configEntity) private StringbuildJsonFromConfigs(Function<AbstractConfigEntity, com.google.gson.JsonObject> extractor) Builds a JSON string from all config entities using the provided extractor function.getAllConfigEntities(UltiToolsPlugin plugin) Get all config entities for a plugin.final StringGet all comments.<T extends AbstractConfigEntity>
List<T> getConfigEntities(UltiToolsPlugin plugin, Class<T> type) Get all config entities by type.<T extends AbstractConfigEntity>
TgetConfigEntity(UltiToolsPlugin plugin, Class<T> type) Get config entity.<T extends AbstractConfigEntity>
TgetConfigEntity(UltiToolsPlugin plugin, String path, Class<T> type) Get config entity by path.final voidloadFromJson(String json) Load config from JSON string.final voidloadFromJson(String configFilePath, String json) Load a single config file from a JSON string.voidregister(UltiToolsPlugin ultiToolsPlugin, AbstractConfigEntity configEntity) Register config entity.voidregisterAll(UltiToolsPlugin plugin, String[] packageNames, ClassLoader classLoader) Registers every@ConfigEntityclass found across MULTIPLE scan packages for one plugin, as ONE atomic batch (CR-01, #358 Part 1 gate-1 finding).voidregisterAll(UltiToolsPlugin plugin, String packageName, ClassLoader classLoader) Register all config entities in the specified package.voidreloadConfigs(UltiToolsPlugin plugin) Reload all configs.private voidrollBackRegisteredEntities(UltiToolsPlugin ultiToolsPlugin, Map<String, AbstractConfigEntity> entitiesToRestore) RestoresultiToolsPlugin's entry inpluginConfigMapto exactlyentitiesToRestore- not just the same key set, but the same path-to-entity VALUES, so a path this call replaced with a different entity before failing is restored to the entity that was actually there before the call started (seesnapshotRegisteredEntities(com.ultikits.ultitools.abstracts.UltiToolsPlugin)'s javadoc for why key-only retention was insufficient).voidsaveAll()Saves, at shutdown, every registered configuration that module code changed in memory.private Map<String, AbstractConfigEntity> snapshotRegisteredEntities(UltiToolsPlugin ultiToolsPlugin) Snapshots the complete config-file-path-to-entity mappingultiToolsPluginalready holds inpluginConfigMap, before a caller is about to register a batch of entities in one call.final StringtoJson()Cast config to JSON format.private voidLogs that the shutdown save wrote an in-memory change over a file that was changed or removed on disk while the server was running (#510).private voidLogs that the shutdown save left a configuration file alone because the framework could not parse it the last time it read it (#510), and that any in-memory change to that configuration was therefore not written.
-
Field Details
-
pluginConfigMap
-
-
Constructor Details
-
ConfigManager
public ConfigManager()
-
-
Method Details
-
register
public void register(UltiToolsPlugin ultiToolsPlugin, AbstractConfigEntity configEntity) throws IOException Register config entity.- Parameters:
ultiToolsPlugin- UltiTools moduleconfigEntity- Config entity- Throws:
IOException
-
addConfigEntity
-
snapshotRegisteredEntities
private Map<String,AbstractConfigEntity> snapshotRegisteredEntities(UltiToolsPlugin ultiToolsPlugin) Snapshots the complete config-file-path-to-entity mappingultiToolsPluginalready holds inpluginConfigMap, before a caller is about to register a batch of entities in one call. Used byregister(UltiToolsPlugin, AbstractConfigEntity)'s directory branch,registerAll(UltiToolsPlugin, String, ClassLoader), andregisterAll(UltiToolsPlugin, String[], ClassLoader)to restore precisely what existed before this call, on a refusal, without touching anything a prior call already committed (#358 Part 1).Gate-1 review finding: an earlier version of this snapshot captured only the KEY set, and
rollBackRegisteredEntities(com.ultikits.ultitools.abstracts.UltiToolsPlugin, java.util.Map<java.lang.String, com.ultikits.ultitools.abstracts.AbstractConfigEntity>)retained those keys viaretainAllrather than restoring their values. That is correct only when a batch never replaces an already- registered path with a different entity instance before failing - but a supported flow (e.g.registerAllscanning a path a module also registered directly viagetAllConfigs()) can do exactly that, perConfigManagerDualPathRegistrationTest. Capturing entity VALUES, not just keys, is what lets a rollback actually restore the original entity rather than silently keeping whatever replaced it.- Parameters:
ultiToolsPlugin- the plugin whose current registrations to snapshot- Returns:
- a defensive copy of the currently-registered path-to-entity map, or an empty map if none
-
rollBackRegisteredEntities
private void rollBackRegisteredEntities(UltiToolsPlugin ultiToolsPlugin, Map<String, AbstractConfigEntity> entitiesToRestore) RestoresultiToolsPlugin's entry inpluginConfigMapto exactlyentitiesToRestore- not just the same key set, but the same path-to-entity VALUES, so a path this call replaced with a different entity before failing is restored to the entity that was actually there before the call started (seesnapshotRegisteredEntities(com.ultikits.ultitools.abstracts.UltiToolsPlugin)'s javadoc for why key-only retention was insufficient). The cleanup half of #358 Part 1's all-or-nothing registration.- Parameters:
ultiToolsPlugin- the plugin to roll backentitiesToRestore- the path-to-entity map to restore, as captured before this call started
-
registerAll
Register all config entities in the specified package.- Parameters:
plugin- UltiTools modulepackageName- Package nameclassLoader- Class loader
-
registerAll
Registers every@ConfigEntityclass found across MULTIPLE scan packages for one plugin, as ONE atomic batch (CR-01, #358 Part 1 gate-1 finding).UltiToolsPlugin.initConfig()callsregisterAll(UltiToolsPlugin, String, ClassLoader)once per surviving entry ofDependencyUtils.getPluginPackages(plugin)- and #362's de-duplication only collapses NESTED scan packages, so two unrelated sibling packages (declared via@ComponentScan(basePackages = {...})or@UltiToolsModule(scanBasePackages = {...})) both survive and are scanned in two SEPARATE calls. Each single-package call's own snapshot/rollback is correctly scoped to not disturb a PRIOR call's successful work - which means a refusal on the SECOND package left the FIRST package's already-committed entries stranded, because neither call's snapshot ever captured "before this plugin's whole scan", only "before this one call". This overload snapshots once, before any package inpackageNamesis scanned, and rolls back to that ONE snapshot if any package's scan refuses - so a plugin whose scan packages span more than one package registers all of them, or none.- Parameters:
plugin- UltiTools modulepackageNames- every package name to scan, in the orderDependencyUtils.getPluginPackagesreturns themclassLoader- Class loader
-
getConfigEntity
Get config entity.- Type Parameters:
T- Config entity type- Parameters:
plugin- UltiTools moduletype- Config entity type- Returns:
- Config entity
-
getConfigEntity
public <T extends AbstractConfigEntity> T getConfigEntity(UltiToolsPlugin plugin, String path, Class<T> type) Get config entity by path.- Type Parameters:
T- Config entity type- Parameters:
plugin- UltiTools modulepath- Config entity pathtype- Config entity type- Returns:
- Config entity
-
getConfigEntities
public <T extends AbstractConfigEntity> List<T> getConfigEntities(UltiToolsPlugin plugin, Class<T> type) Get all config entities by type.- Type Parameters:
T- Config entity type- Parameters:
plugin- UltiTools moduletype- Config entity type- Returns:
- Config entity list
-
getAllConfigEntities
Get all config entities for a plugin.- Parameters:
plugin- UltiTools module- Returns:
- All config entities
-
reloadConfigs
Reload all configs.- Parameters:
plugin- UltiTools module
-
saveAll
public void saveAll()Saves, at shutdown, every registered configuration that module code changed in memory.Since 6.3.0 (#510) this writes only the entities whose
AbstractConfigEntity.isModifiedSinceSnapshot()istrue- whose current state differs from the state they last loaded or saved. A configuration no code changed is left alone, so an operator's edit to its file made while the server was running survives the restart. Before 6.3.0 every file was rewritten from memory and such edits were silently discarded.A configuration whose file failed to parse the last time it was read is never written: the framework does not know what that file holds, so overwriting it would destroy an operator's broken file. One WARNING per such file says so, and any in-memory change to it is not written.
If an entity was changed in memory and its file was also changed on disk since that snapshot, the in-memory state still wins and is written, and a WARNING names the file whose edits were overwritten. The only caller is
UltiTools#onDisable(); an explicitAbstractConfigEntity.save()is unaffected and always writes.Each entity's check-then-save runs under that entity's own monitor, the lock its read, write and snapshot paths also hold, so a panel write still in flight on the WebSocket thread is applied either wholly before or wholly after this entity's shutdown save. A save failure, or any unchecked exception from one entity, is logged and does not stop the remaining entities from being saved.
-
warnUnparseableFileLeftAlone
Logs that the shutdown save left a configuration file alone because the framework could not parse it the last time it read it (#510), and that any in-memory change to that configuration was therefore not written.- Parameters:
config- the entity that was skipped
-
warnOperatorEditOverwritten
Logs that the shutdown save wrote an in-memory change over a file that was changed or removed on disk while the server was running (#510). The overwrite itself is the documented contract - a value set from code is saved on disable - but it must not be silent.- Parameters:
config- the entity that was just saved
-
buildJsonFromConfigs
private String buildJsonFromConfigs(Function<AbstractConfigEntity, com.google.gson.JsonObject> extractor) Builds a JSON string from all config entities using the provided extractor function.- Parameters:
extractor- function to extract JsonObject from config entity- Returns:
- JSON string
-
getComments
Get all comments.- Returns:
- all comments
-
toJson
Cast config to JSON format.- Returns:
- config in JSON format
-
loadFromJson
Load config from JSON string.Since 6.3.0, a value violating its
@Range/@NotEmpty/@Size/@Patternconstraint refuses withConfigurationExceptioninstead of being written - the operator's file is not modified for that config entity (SILENT-14).Since gate-1 CR-02 (#358 Part 2), the WHOLE batch this call touches - potentially several config entities across several plugins in one JSON payload - is VALIDATED before any of them is persisted: a first pass calls
AbstractConfigEntity.validateProposedProperties(com.google.gson.JsonObject)on every touched entity (applying nothing to disk, restoring every field it touched regardless of outcome), and only once every entity in the batch has passed does a second pass callAbstractConfigEntity.updateProperties(com.google.gson.JsonObject)on each to actually apply and persist. A validation refusal on entity N therefore leaves entities 1..N-1 exactly as they were before this call - none of them written to disk - rather than the pre-CR-02 behaviour where files 1..N-1 were already applied and persisted by the time entity N's refusal was discovered.This guarantee covers VALIDATION refusals only, not a physical I/O failure during the second pass's own persist step (gate-1 review, line 425): if entity K's own
config.save(File)throwsIOException- a disk-full or permissions failure, not a validation constraint - entities 1..K-1 have already been applied and persisted by that point, and this call still throws, leaving a partially-applied batch. Making the persist phase itself durable against a physical write failure across N independent files would need staged writes (temp file + atomic rename) or a byte-level undo log for every touched file, which is a materially larger change than this fix's scope (see the follow-up issue filed for it). This is the same shape as #469 (WR-01): the registry-level guarantee this class makes is not a filesystem-durability guarantee.- Parameters:
json- JSON string- Throws:
IOException- if an I/O error occurs while persisting - entities already persisted earlier in this batch are NOT rolled backConfigurationException- if a value violates its validation constraint - nothing in this call's batch is persisted when this is thrown, since validation runs to completion across the whole batch before persistence starts
-
loadFromJson
Load a single config file from a JSON string.loadFromJson(String)accepts the full nested structure{pluginName: {configPath: {key: value}}}-- the same shapetoJson()produces. This method accepts just the innermost layer -- one config file's own{key: value}map -- and writes it to whichever fileconfigFilePathnames. The panel uses this narrower shape when pushing a single config by filename; see issue #236.A config path is unique within a single plugin (it is the inner key of
pluginConfigMap), but may collide across plugins. Both "not found" and "matched more than one" throw rather than silently doing nothing -- "the caller thinks it wrote something and nothing actually happened" was exactly the original defect on this path.Since 6.3.0, a value violating its
@Range/@NotEmpty/@Size/@Patternconstraint refuses withConfigurationExceptioninstead of being written - the operator's file is not modified (SILENT-14).- Parameters:
configFilePath- config file path as registered, e.g.config/lang.ymljson- JSON object of that file's entries- Throws:
IOException- if the path is blank, unknown, ambiguous, or the JSON is not an object, or if saving failsConfigurationException- if a value violates its validation constraint- Since:
- 6.2.5
-