Class CleanupJcrStorage
CleanupJcrStorage against the collaboration
workspace, always through a system session (async cleanup workers carry no
user conversation state).
SESSION LIFECYCLE — getSystemSession() opens a BRAND-NEW system
session on every call, so every method obtaining one MUST release it in a
finally block through logout(Session). The per-item
primitives (revalidate / addExemptionMixin / removeExemptionMixin /
deleteNode / purgeVersions) are invoked once per campaign item: leaking one
session per call would leave tens of thousands of live sessions behind on a
large campaign. scanRoot(java.lang.String, java.lang.String, int, org.exoplatform.addons.documents.cleanup.model.CleanupParams, org.exoplatform.addons.documents.cleanup.storage.CleanupJcrStorage.ScanBatchConsumer) holds its session for the WHOLE streamed
walk (the NodeIterator is lazy) and logs out only once the iteration
is over, never inside the batch loop.
THE ONE EXCEPTION is the observation listener: a JCR listener is bound to the
session that registered it, so logging that session out would silently kill
the listener. registerObservationListener(BiConsumer) therefore
keeps ONE dedicated long-lived session in observationSession, logged
out only by unregisterObservationListener() (or by a failed
registration).
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic interfaceReceives the scanned batches streamed byscanRoot(String, String, int, CleanupParams, ScanBatchConsumer). -
Field Summary
Fields -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionaddExemptionMixin(String nodeUuid, String username) Adds the exo:cleanupExemption mixin on the node, keeping real modification dates untouched.longcountFiles(String rootPath) deleteNode(String nodeUuid) Hard-deletes a file node (no trash): removes pointing symlinks first, then the node, then now-empty ancestor folders up to (excluding) the drive root.Enumerates the PARTITIONS of a dry-run scan — the subtrees a reader thread each walks on its own — by applying the per-root policy ofCleanupConstants: the direct children of a split root (CleanupConstants.SPLIT_SCAN_ROOTS) become units, an unsplit root (CleanupConstants.UNSPLIT_SCAN_ROOTS) is itself one unit.purgeVersions(String nodeUuid, CleanupParams params) Purges the versions of a file selected byCleanupCriterionEvaluator.selectVersionsToRemove(java.util.Map<java.lang.String, java.lang.Long>, java.lang.String, java.lang.String, org.exoplatform.addons.documents.cleanup.model.CleanupParams, long): those older than the campaign period by their OWN creation date, plus — of the versions remaining inside the period — the oldest ones still exceedingmaxVersionsPerFile.booleanregisterObservationListener(BiConsumer<String, String> pathAndEventTypeCallback) Registers a JCR observation listener forwarding (nodePath, eventType) pairs for changes under the scanned roots.removeExemptionMixin(String nodeUuid) Removes the exo:cleanupExemption mixin from the node (un-keep), mirroringaddExemptionMixin(String, String): a checked-in versionable node is checked out first, and real modification dates stay untouched.revalidate(String nodeUuid, CleanupParams params) Re-evaluates a node against the campaign criteria, at execution or refresh time.voidscanRoot(String rootPath, String resumeAfterPath, int batchSize, CleanupParams params, CleanupJcrStorage.ScanBatchConsumer batchConsumer) Scans the path-ordered nt:file nodes under the given root with a SINGLE query, streaming the qualifying cleanup candidates to the consumer in batches ofbatchSizescanned nodes (the last batch may cover fewer).voidUnregisters the JCR observation listener, if registered, and logs out the session that owned it.
-
Field Details
-
COLLABORATION
- See Also:
-
EXO_CLEANUP_EXEMPTION
- See Also:
-
EXO_CLEANUP_EXEMPTED_DATE
- See Also:
-
EXO_CLEANUP_EXEMPTED_BY
- See Also:
-
-
Constructor Details
-
CleanupJcrStorage
public CleanupJcrStorage()
-
-
Method Details
-
listScanUnits
Enumerates the PARTITIONS of a dry-run scan — the subtrees a reader thread each walks on its own — by applying the per-root policy ofCleanupConstants: the direct children of a split root (CleanupConstants.SPLIT_SCAN_ROOTS) become units, an unsplit root (CleanupConstants.UNSPLIT_SCAN_ROOTS) is itself one unit. Depth 1 only — read the SPLIT_SCAN_ROOTS comment on what a direct child of /Users really is before touching this.A root that does not exist is SKIPPED without failing the scan (/Trash is legitimately absent on a fresh instance), and the paths come back sorted so that re-planning a resumed campaign is deterministic.
- Returns:
- the planned unit paths, sorted
- Throws:
IllegalStateException- on a JCR failure, so the caller leaves the campaign resumable instead of scanning a partial tree
-
countFiles
- Parameters:
rootPath- scanned tree root path (e.g. /Users or /Groups/spaces)- Returns:
- total number of nt:file nodes under the given root
- Throws:
IllegalStateException- on a JCR failure — the idiom of its siblingsscanRoot(java.lang.String, java.lang.String, int, org.exoplatform.addons.documents.cleanup.model.CleanupParams, org.exoplatform.addons.documents.cleanup.storage.CleanupJcrStorage.ScanBatchConsumer)andlistScanUnits(), and load-bearing here: a swallowed failure answered 0, which the estimation persisted as a FINAL unit total (the schema separates NULL 'never counted' from 0 'counted, empty'), so a transient hiccup permanently amputated the campaign denominator. Propagating fails the estimation future instead, the run aborts resumable, and TOTAL_COUNT stays NULL for the retry to re-count
-
scanRoot
public void scanRoot(String rootPath, String resumeAfterPath, int batchSize, CleanupParams params, CleanupJcrStorage.ScanBatchConsumer batchConsumer) Scans the path-ordered nt:file nodes under the given root with a SINGLE query, streaming the qualifying cleanup candidates to the consumer in batches ofbatchSizescanned nodes (the last batch may cover fewer). When resuming, the result is fast-forwarded BY PATH: nodes whose path is not strictly greater thanresumeAfterPathare skipped without processing. Unlike a numeric offset, this stays correct when the tree changed during the interruption (no file shifted before the cursor is silently missed), including when the checkpointed node itself no longer exists.- Parameters:
rootPath- scanned tree root pathresumeAfterPath- path of the last node processed before the interruption (resume checkpoint), null or blank for a fresh scanbatchSize- number of scanned nodes per consumer invocationparams- campaign parameters snapshotbatchConsumer- receives each scanned batch; returns false to abort the scan of this root- Throws:
IllegalStateException- on a JCR failure, so the caller can leave the scan resumable from its last persisted checkpoint
-
revalidate
Re-evaluates a node against the campaign criteria, at execution or refresh time. A transient JCR read failure yields a distinct UNKNOWN outcome — never 'spared' nor 'gone' — so a flaky repository can neither permanently spare an item nor let it be deleted on doubt: the node lookup is made directly against the session (not through the null-swallowing helper) to tell 'the node no longer exists' apart from 'the repository failed'.- Parameters:
nodeUuid- JCR node identifierparams- campaign parameters snapshot- Returns:
- revalidation outcome (gone, exempted, spared, still candidate, or unknown on a JCR read failure)
-
addExemptionMixin
Adds the exo:cleanupExemption mixin on the node, keeping real modification dates untouched.- Parameters:
nodeUuid- JCR node identifierusername- user who decided to keep the file- Returns:
CleanupExemptionResult.ADDEDwhen the node is now exempted,CleanupExemptionResult.NOT_FOUNDwhen it doesn't exist anymore,CleanupExemptionResult.FAILEDon a (possibly transient) JCR read or write failure — never NOT_FOUND on doubt, so the user's keep decision is never discarded
-
removeExemptionMixin
Removes the exo:cleanupExemption mixin from the node (un-keep), mirroringaddExemptionMixin(String, String): a checked-in versionable node is checked out first, and real modification dates stay untouched.- Parameters:
nodeUuid- JCR node identifier- Returns:
CleanupExemptionResult.ADDEDwhen the node no longer carries the mixin (applied, idempotent),CleanupExemptionResult.NOT_FOUNDwhen it doesn't exist anymore,CleanupExemptionResult.FAILEDon a (possibly transient) JCR read or write failure — never NOT_FOUND on doubt, so a flaky repository never marks a still-existing file GONE
-
deleteNode
Hard-deletes a file node (no trash): removes pointing symlinks first, then the node, then now-empty ancestor folders up to (excluding) the drive root.- Parameters:
nodeUuid- JCR node identifier- Returns:
- purge outcome with reclaimed bytes, GONE only when the node really
disappeared, or SKIPPED with a reason (a JCR read failure is
SKIPPED, never GONE). A failure happening AFTER the removing
session.save()— the empty-ancestor cleanup is the only step left — leaves the item SKIPPED (an administrator must still see the file needed attention) but CARRIES the bytes of the file that is really deleted, exactly aspurgeVersions(String, CleanupParams)does for the versions it already removed: dropping them would under-report the campaign's reclaimed total
-
purgeVersions
Purges the versions of a file selected byCleanupCriterionEvaluator.selectVersionsToRemove(java.util.Map<java.lang.String, java.lang.Long>, java.lang.String, java.lang.String, org.exoplatform.addons.documents.cleanup.model.CleanupParams, long): those older than the campaign period by their OWN creation date, plus — of the versions remaining inside the period — the oldest ones still exceedingmaxVersionsPerFile. The root and base (current) versions are never touched, so the file itself always keeps its content.The removal set is computed by the SAME pure policy the scan and the revalidation apply, so what an administrator was shown as purgeable is exactly what is removed here.
- Parameters:
nodeUuid- JCR node identifierparams- campaign parameters snapshot, carrying the period and the per-file version cap the policy unions- Returns:
- purge outcome with reclaimed bytes, GONE only when the node really
disappeared, or SKIPPED with a reason (a JCR read failure is
SKIPPED, never GONE). A version removal failing PARTWAY leaves the
item SKIPPED — an administrator must still see the file needs
attention — but the bytes already reclaimed by the removals that DID
succeed are carried on the result:
removeVersionis immediate, so those bytes are really gone and dropping them would under-report the campaign's reclaimed total.
-
registerObservationListener
Registers a JCR observation listener forwarding (nodePath, eventType) pairs for changes under the scanned roots. Idempotent. Unlike every other method here, the session obtained is NOT logged out: the listener is bound to it for its whole lifetime, so it is kept inobservationSessionand released only byunregisterObservationListener().- Parameters:
pathAndEventTypeCallback- callback receiving the event node path and the event type name- Returns:
- true when the listener is registered (or already was), false when the registration failed (e.g. JCR not ready yet) and may be retried
-
unregisterObservationListener
public void unregisterObservationListener()Unregisters the JCR observation listener, if registered, and logs out the session that owned it. Idempotent.
-