Addendum for Export Refactoring PR - #12594
Draft
poikilotherm wants to merge 39 commits into
Draft
Conversation
…istryBean IQSS#11405 - Moved exporter management logic into a dedicated `ExporterRegistryBean` singleton for improved modularity and maintainability. - Simplified `ExportService` to delegate exporter logic to the new registry.
- Enable injectingthe registry and other components - The export process itself is stateless. State is involved in potential write locks, the loaded plugins, etc. - A stateless coordinator bean scales better for multiple export requests coming in.
…alidator, and storage abstraction IQSS#11405 The goal is removing the caching logic from the ExportService. At the same time, a distinct caching subsystem shall have policies about what gets cached, when it expires etc, all independent of a coordinating service like ExportService. This make cognitive loader smaller and allows extension without using more code branches.
…e into FileEmbargoExpiryInvalidator IQSS#11405
…eIOCache class IQSS#11405 - Reorganized export cache handling into a dedicated `StorageIOCache` service, improving modularity and reducing cognitive load in `ExportService`. - Streamlined caching operations with a unified approach across all storage drivers. - Deprecated legacy unversioned cache keys; introduced versioned aux tag schema for better cache qualification. - Enhanced write atomicity and cache eviction logic. - Remove stale code for size of exports
…OCache IQSS#11405 The legacy reading of cached exports is prone to produce bugs in production. When we rely on reading cached exports as prerequisites for other metadata formats, we might end up with stale data. Any export has no knowledge about whether and when an export of another format happened. We keep no provenance per format. Assuming there is a cached "latest" with the legacy file format, it would be read as a prerequisite format, but our invalidation mechanisms would not be able to tell if it's actually stale, because it was not yet re-exported. Any released version is immutable, thus if we rely in lookups on cached objects with the version present in the aux tag, we can be sure we get the latest data.
…constructor - Added null and blank checks for dataset, version, and formatName to ensure robust usage. - Introduced a convenience constructor for creating cache keys directly from a dataset version and format.
…rvice` package and rename `ExportService` to `ExportServiceBean` - "ExportServiceBean" is more aligned with the codebase style where EJBs mostly have a "Bean" name suffix. - Also move test classes into the same package (under the test source tree)
…1405 - Documented `tryRead`, `deleteQuietly`, and `storageFor` with proper Javadoc. - Clarified the stream-closing intent in `write` to make the leak-avoidance pattern explicit.
…to ExportServiceBean IQSS#11405 - Relocated the `invalidators` collection from the sealed interface to the service bean, where it logically belongs as a runtime dependency rather than a static on the contract. - Added a section marker for export data retrieval methods in `ExportServiceBean`. - Noted future plan to replace the static list with a registry pattern once plugins can supply their own invalidation logic.
…tServiceBean Making it simpler to read inline.
Added `ExportCache` as an CDI (not EJB) injected dependency in the service bean.
- Introduced `clearCachedFormats(DatasetVersion, List<String>)` as the version-specific clearing entry point, with the dataset-level overload delegating via a new `defaultVersion()` helper. - Added `clearCachedFormat(DatasetVersion, String)` to evict a single cache entry by key. - Added `requireExists` and `requireAllExist` validation methods to `ExporterRegistryBean` so format names are checked before eviction.
…QSS#11405 Align the related methods into one block, not divided by the cache handling stuff.
…istryBean IQSS#11405 - Added `buildFormatRequiredByMap` to build a read-only map of prerequisite format names to the exporters that depend on them. - Added `buildAndVerifyRequirements` to validate registry integrity: all prerequisite formats must have a registered exporter, and no cyclic prerequisite chains may exist. - Integrated the check into initialization as Step 4, failing fast with `ExportException` on any integrity violation (missing prerequisite or cycle).
…#11405 Added `formatRequiredBy` field to store the prerequisite format dependency map alongside the exporters map, populated during registry initialization. Will be reused during cascaded cache eviction or exporting of formats depending on a certain format.
…#11405 - Added `buildPrerequisitesChainDepth` to compute the prerequisite chain depth for each format (0 = no prerequisite, N = N levels deep). - Added `buildTopologicalComparator` to create an immutable comparator ordering exporters by depth, with format name as tiebreaker for deterministic results. - Exposed via `getTopologicalComparator()` so callers can sort the exporter list in a dependency-safe order. - Integrated as Step 5 in initialization, stored alongside the existing `formatRequiredBy` map.
…IQSS#11405 - Added `SecureTempFiles` utility that creates temp files with `0600` permissions on POSIX systems; on Windows it relies on the per-user `%TEMP%` ACLs. - Replaced raw `Files.createTempFile` in `StorageIOCache.write` with `SecureTempFiles.createOwnerOnlyTempFile` so other local users can no longer read or tamper with export temp files.
poikilotherm
force-pushed
the
11405-addendum
branch
from
August 20, 2026 09:44
f3f92aa to
8f1d928
Compare
The cache key should not be responsible to carry the information about the "where" of an export, just about the "what". Changing dependent methods accordingly. Also, fixed ambiguity with the cache invalidator implementations: the invalidator should look for stale *versions* of dataset, not for the dataset as a whole being stale. The cache is treating versions individually, so they shall get stale individually, too. - Reduced `ExportCacheKey` to a single `auxTag` string, removing `Dataset`/`DatasetVersion` references for thread-safety and GC-friendliness. - Moved `TAG_PREFIX`/`TAG_SUFFIX` into `ExportCacheKey` as public constants. - Added explicit `Dataset` parameter to all `ExportCache` methods (`read`, `write`, `evict`) since the key no longer carries storage context. - Added explicit `DatasetVersion` parameter to `ExportCacheInvalidator.isStale`; updated `FileEmbargoExpiryInvalidator` with null-checks and released/archived status guard. - Updated `StorageIOCache` logging to use `dataset.getId()` instead of the version string.
…ndents set IQSS#11405 - Renamed `formatRequiredBy` to `transitiveDependents`, changing the value type from `List<String>` to `Set<String>` to capture all direct and transitive dependents per format. - Replaced `buildPrerequisitesChainDepth` with `buildTransitiveDependents`, which walks each exporter's prerequisite chain and registers it as a dependent of every ancestor format. - Updated `buildTopologicalComparator` to sort by new dependent-set - Merged `buildFormatRequiredByMap` into `verifyRequirements` as the former map is no longer stored for reuse - Moved `getFormatsDependingOn` to `getTransitiveDependents` to reflect the new semantics
…QSS#11405 - Introduced sealed `Details` interface exposing `localizedDisplayName`, `formatName`, `mediaType`, `isHarvestable`, and `isAvailableToUsers`, thus avoiding having to retrieve these details from the exporter, saving a roundtrip. - Made `ExporterDetails` record package-private to prevent external instantiation while allowing consumers to read via the interface. - Renamed `getLabels()` to `getDetails()`, returning `List<Details>` with the expanded field set. - Added `get(Details)` lookup method to resolve an exporter by its details object. These can only be created and handed out by the registry, thus we can be sure a matching exporter exists. - Removed unused `Collections` import.
…tCacheKey components IQSS#11405 - Replaced the single `auxTag` field with `formatName` and `friendlyVersion` so the key exposes its meaningful parts directly. - Moved `auxTag()` from a static factory into an instance method derived from the record's fields. - Split validation into `checkFormatName` and `checkVersion` private helpers for clearer intent (and compatibility with the constructor needing to be called first thing).
…ServiceBean IQSS#11405 These methods (`getExporter`, `isXMLFormat`, `getMediaType`) directly exposed the internal `exporterMap` and are no longer needed now that format details are resolved via the `Details` interface in the registry.
…IQSS#11405 Added null check in `get(String formatName)` to return `Optional.empty()` instead of throwing NPE when the underlying Map implementation does not permit null keys.
…ation - Tracks consecutive failures via an `AtomicInteger` streak; escalates from `FINE` to `WARNING` once the streak reaches the configured threshold. - A success resets the streak; a threshold of zero or negative deactivates escalation entirely. - Thread-safe and suitable for sharing across concurrent callers or use in `ConcurrentHashMap` contexts. - Warnings will not be flooding the log once threshold is reached via configurable repeat cycle. - To enable "all clear" messages once the threshold was met, the success recording may then return the number of failures. Using OptionalInt, the logging statement is a one-liner.
…vadoc IQSS#11405 - Clarified that the legacy unqualified name is ignored for read/write cycles and only purged via `evictAll`, rather than being a read fallback. - Fix typos
…SS#11405 - Replace static `FINE`-level logging in `tryRead` and `deleteQuietly` with threshold-based escalation via `FailureEscalation` instances (threshold: 256). - Log a recovery warning once consecutive failures drop below the threshold after previously exceeding it. - Include the current failure streak in the read-path log message for operational context.
…EJB IQSS#11405 - Introduces a `@Stateless` EJB that funnels all export data production (draft, cached, bulk) through a single path for uniform staleness validation, prerequisite resolution, and error wrapping. - Cached reads consult registered `ExportCacheInvalidator` instances; stale entries are evicted and reported as a miss. - Prerequisite formats are resolved recursively with circular-chain detection via an in-flight `LinkedHashSet`. - Non-cacheable (draft) versions are produced to `SecureTempFiles` with `DELETE_ON_CLOSE` to avoid in-memory retention of large exports. - `IllegalStateException` from exporters is wrapped in `ExportException` with dataset context for consistent reporting across all production paths.
…QSS#11405 - Injecting `ExportPipelineBean` as an `@EJB` - Removed the static `invalidators` list and its associated Javadoc from `ExportServiceBean` - they are now owned by the pipeline.
…IQSS#11405 - Introduces a static `isCacheable(DatasetVersion)` method so the "drafts are mutable, therefore never cached" rule lives in one place instead of being re-encoded at each call site. - The service owns the cache policy, it's mostly applied within ExportPipeline. - Javadoc documents the intent and flags the method as the extension point for future version states (e.g. deaccessioned).
…l ordering IQSS#11405 - Replace the manual prerequisite-resolution loop with a pipeline-driven `exportFormats(DatasetVersion, List)` that resolves transitive dependents via the registry and sorts exporters topologically before executing `produceAndCache` in ExportPipeline. - Simplify `exportFormat(Dataset, String)` to a one-line delegate over `exportFormats` with a single-element list. - Update `lastExportTime` after any successful export, not only when the full format set was requested. - Collect per-format failures and throw a single `ExportException` at the end, logging each individual failure at WARNING level.
…QSS#11405 - `ExportServiceBean#getExport` now simply attempts `readFreshCachedExport` and falls back to `readFreshExport`, eliminating the manual draft/published branching and the in-memory `ByteArrayOutputStream` round-trip. - Replaces `ExportPipelineBean#produceAndWriteOut` (caller-supplied `OutputStream`) with `readFreshExport`, which produces to a `SecureTempFiles` temp file and returns an `InputStream`, consistent with the existing temp-file strategy for drafts. - Renames `producePreReqToTempFile` to `produceToTempFile` since it now serves both prerequisite and primary format paths uniformly.
…1405 Added TODO comments in `readFreshCachedExport` flagging that the per-invalidator staleness check is a naive approach that won't scale properly to longer prerequisite format chains.
…ontext injection IQSS#11405 - Add `exportService()` and `exporterRegistry()` to `CommandContext`, implemented via EJB lookup in `EjbDataverseEngine` and null stubs in `TestCommandContext`. - Replace all `ExportService.getInstance()` call sites in `CuratePublishedDatasetVersionCommand`, `RedetectFileTypeCommand`, `DeaccessionDatasetVersionCommand`, and `DestroyDatasetCommand` with `ctxt.exportService()`. - Remove now-unnecessary `ExportService` imports from the affected command classes. - Widen `clearAllCachedFormats` catch from `IOException` to `ExportException` and add WARNING-level logging for ignored export failures.
…beans IQSS#11405 - Inject `ExportServiceBean` and `ExporterRegistryBean` via `@EJB` in `OAIServlet` and `OAIRecordServiceBean`. - Replace all `ExportService.getInstance()` call sites in `OAIServlet`, `OAIRecordServiceBean`, and `DataverseXoaiItemRepository` with injected bean calls. - Add `exportService` as a constructor injection parameter to `DataverseXoaiItemRepository` (it's a POJO). - Simplify `addSupportedMetadataFormats` to iterate `exporterRegistryService.getAll()` directly, removing manual label lookup and null-checking. - Add TODO comments in `OAIRecordServiceBean#exportAllFormats` questioning silent exception swallowing. - Remove unused imports
…beans IQSS#11405 - Inject `ExportServiceBean` and `ExporterRegistryBean` via `@EJB` in `AbstractApiBean`. - Replace `ExportService.getInstance()` call in `Files#exportDatasetMetadata` with injected `exportSvc`. - Replace manual label-lookup validation in `Metadata#validateFormatNames` with `exporterRegistrySvc.requireAllExist(formatNames)`. - Reformat `Info#getExportFormats` to iterate `exporterRegistrySvc.getDetails()` instead of `ExportService.getInstance().getExportersLabels()`. - Remove unused imports.
…njected beans IQSS#11405 - Inject `ExporterRegistryBean` via `@EJB` in `DatasetPage` and pass it to `SignpostingResources`. - Replace `ExportService.getInstance().getExportersLabels()` loops with `exporterRegistry.getDetails()` iteration in both the `describedby` header and the linkset JSON. - Simplify `describedby` construction using a shared template string and `StringBuilder`. - Replace `mediaTypes.toString().isBlank()` with `mediaTypes.build().isEmpty()` for a more accurate emptiness check. - Remove unused imports (`ExportService`, `Json`).
…eans in FilePage IQSS#11405 - Inject `ExportServiceBean` and `ExporterRegistryBean` via `@EJB` in `FilePage`. - Rewrite from using `getExporters()` to stream over `exporterRegistryService.getDetails()`, replacing the manual `ExportService.getInstance().getExportersLabels()` loop and per-exporter null-checking. - Replace `ExportService.getInstance().exportAllFormats()` with the injected `exportService.exportAllFormats()`. - Remove unused imports
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.