Class CleanupCampaignService
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final StringFile-store namespace the archived CSV reports live in.static final Set<CleanupCampaignState> States in which the GRACE PERIOD of a campaign may still be edited.static final intMaximum campaign name length, mirroring the NAME column of DOCUMENTS_CLEANUP_CAMPAIGN —NVARCHAR(250) NOT NULL.static final longRETRIES an item may spend AFTER its initial purge attempt — three of them, so a doomed item is purge-attempted four times in all.static final StringLocalizable message code reported for an ACL refusal in a bulk outcome, in place of theIllegalAccessExceptionraw English message.Failure message codes a retry may re-attempt. -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionvoidScheduled-glue entry point: keeps item detail for the last N terminal campaigns only; older reports are archived as CSV (FileService) then their item detail is purged.voidcancelCampaign(long campaignId) Cancels a campaign from any non-terminal state.voidcheckArchiveAvailable(long campaignId) Availability check of a campaign's CSV report, to run BEFORE a single byte is streamed: once the response body started flowing the HTTP status is already committed, so the not-found outcome has to be settled here.compareCampaigns(long baseCampaignId, long otherCampaignId) Delta between two campaigns' candidate sets, matched by node uuid.createCampaign(String name, CleanupParams overrides) Creates a campaign snapshotting the effective parameters, then launches its dry-run scan.voiddeleteCampaign(long campaignId) Deletes a campaign and everything hanging off it: its item rows, its scan unit rows and its archived CSV.voiddeleteCampaignRowsTransactional(long campaignId) Drops the report and scan unit rows of an already-deleted campaign, off the request thread.executeCampaign(long campaignId) Triggers the batched purge of a LOCKED campaign.getCampaign(long campaignId) getCampaignFailures(long campaignId) Grouped failures of a campaign: one entry per distinct failure message code, its SKIPPED item count, and whether a retry would re-attempt it — the flag read fromRETRYABLE_FAILURE_REASONS, so the console never has to hold its own copy of that rule.org.springframework.data.domain.Page<CleanupCampaignItem> getCampaignItems(long campaignId, Long ownerIdentityId, CleanupItemState state, CleanupAction action, Long minSize, String search, org.springframework.data.domain.Pageable pageable) Campaign items with optional filters, for the admin console.getCampaignScanFailures(long campaignId) Grouped failures of a campaign's dry-run SCAN: one entry per distinct failure message code, with the number of SUBTREES that carry it.getCampaignScanUnitProgress(long campaignId) Per-unit breakdown of a campaign's dry run, resolving the campaign here so an unknown id answers 404 like every other campaign endpoint.intorg.springframework.data.domain.Page<CleanupCampaignItem> getMyItems(String username, String search, org.springframework.data.domain.Pageable pageable) Items of the currently relevant campaign owned by the user (own files and files of spaces they manage).getMyItemsSummary(String username) Per-user summary of the currently relevant campaign.voidinit()voidUser decision to keep a candidate file: checks ownership (own file, or manager of the owning space), adds the exemption mixin and marks the item EXEMPTED.Bulk variant ofkeepItem(long, String), continuing past individual failures and REPORTING them: a caller that answered a blanket success would tell the user their files are kept while none of them is.voidScheduled-glue entry point: flips the PUBLISHED campaign to LOCKED once its grace deadline elapsed.publishCampaign(long campaignId) Publishes a SIMULATED campaign, starting its grace period.voidrefreshCandidate(String itemPath, String eventType) Freshness refresh (UX only, not the correctness guarantee), called by the JCR observation glue while a campaign is PUBLISHED.voidResumes the workers of the campaigns left DRY_RUN_RUNNING or EXECUTING without a live worker — the dry-run scan resumes from its persisted path checkpoint, the purge is naturally resumable (it iterates the remaining CANDIDATE items).retryCampaign(long campaignId) Re-attempts the RETRYABLE failed items of a COMPLETED campaign: no new scan, no new grace period, no new campaign.voidshutdown()voidDrops the item and scan unit rows whose campaign row no longer exists.voidunkeepItem(long itemId, String username) User decision to un-keep a previously kept file (undo ofkeepItem(long, String)): checks ownership (own file, or manager of the owning space), removes the exemption mixin, then revalidates the node through the shared revalidation mapping — still qualifying goes back to CANDIDATE, modified meanwhile to SPARED_BY_MODIFICATION, disappeared to GONE.unkeepItems(List<Long> itemIds, String username) Bulk variant ofunkeepItem(long, String), continuing past individual failures and REPORTING them (seekeepItems(List, String)).updateCampaign(long campaignId, String name, Integer graceDays) PARTIAL update of a campaign's editable attributes: its name, its grace period, or both.voidwriteArchiveCsv(long campaignId, OutputStream outputStream) Streams the CSV report of a campaign: generated page by page while item detail is retained (each page is flushed, so the first rows reach the client immediately and the whole report is never materialized in memory), copied from theFileServicearchive afterwards.
-
Field Details
-
FILE_NAMESPACE
File-store namespace the archived CSV reports live in.It MUST be registered with
NameSpaceServicebefore a file is written under it:DataStorage#createlooks the namespace up by name and dereferences the result without a guard, so an unregistered name fails with a raw NullPointerException wrapped in a FileStorageException — 'Error while writing file cleanup-campaign-N.csv', once per retention tick, forever. Every archive attempt failed that way, which also meant the retention job never dropped a single item row: it archives BEFORE purging, deliberately, and the archive never succeeded. SeeensureFileNamespace().- See Also:
-
NOT_OWNER_FAILURE_CODE
Localizable message code reported for an ACL refusal in a bulk outcome, in place of theIllegalAccessExceptionraw English message.- See Also:
-
MAX_NAME_LENGTH
public static final int MAX_NAME_LENGTHMaximum campaign name length, mirroring the NAME column of DOCUMENTS_CLEANUP_CAMPAIGN —NVARCHAR(250) NOT NULL. Nothing used to check it, so a longer name reached the INSERT and failed with a raw database error instead of a 400 carrying a message code the console can localize.- See Also:
-
RETRYABLE_FAILURE_REASONS
Failure message codes a retry may re-attempt. TRANSIENT causes only: a JCR read that failed once may succeed now, a delete that hit a locked node may not, an unexpected error is by definition unclassified. Deliberately ABSENT:cleanup.referentialIntegrity(another node points at the file — the repository will refuse identically) andcleanup.notVersionable(the node has no version history — it never will grow one by itself). Re-running those is guaranteed wasted work on possibly hundreds of thousands of rows.Public because the REST layer flags each grouped failure with it and the tests assert against it: what is retryable is a SERVER decision, and the client must never be the authority on it.
-
GRACE_EDITABLE_STATES
States in which the GRACE PERIOD of a campaign may still be edited. The grace period is the ONE parameter that is not a candidacy criterion — it is read in a single place, to derive the lock date from the publication date — so editing it can never invalidate the dry-run the administrator is reviewing, and the guard is deliberately narrower than 'any state' rather than absent.LOCKED is REJECTED on purpose: extending the grace of a locked campaign would mean going back to PUBLISHED, an edge that does NOT exist in
CleanupCampaignLifecycle.ALLOWED_TRANSITIONS— and exiting PUBLISHED unregisters the freshness observation listener, so a reverse edge would have to re-register it. Out of scope; do NOT 'fix' this casually. EXECUTING, COMPLETED and CANCELLED are rejected because the grace period is MEANINGLESS once the purge ran.Public because the tests and any future caller must share this rule rather than restate it.
-
MAX_RETRY_ATTEMPTS
public static final long MAX_RETRY_ATTEMPTSRETRIES an item may spend AFTER its initial purge attempt — three of them, so a doomed item is purge-attempted four times in all. The initial attempt spends no attempt: only the requeue increments the counter, and the requeue filter isattemptCount < 3.Deliberately NOT the same arithmetic as
CleanupScanService#MAX_SCAN_UNIT_ATTEMPTS, which bounds THREE walks in total: a scan unit spends an attempt on its very first walk, because the coordinator claiming a unit is what increments it. Same value, same purpose — bounding a retry so it cannot loop forever — counted from a different origin, and neither is a typo of the other.Bounds the whole item retry mechanism: past it, a failure has proved itself deterministic whatever its code says, and an administrator re-clicking Retry must not be able to loop on it forever.
- See Also:
-
-
Constructor Details
-
CleanupCampaignService
public CleanupCampaignService()
-
-
Method Details
-
shutdown
@PreDestroy public void shutdown() -
init
@PostConstruct public void init() -
resumeStalledWorkers
public void resumeStalledWorkers()Resumes the workers of the campaigns left DRY_RUN_RUNNING or EXECUTING without a live worker — the dry-run scan resumes from its persisted path checkpoint, the purge is naturally resumable (it iterates the remaining CANDIDATE items). Called at startup recovery AND on every watchdog tick, so a worker thread that died mid-run is re-launched without a JVM restart. Safe to call unconditionally: the workers' running-campaign guard (the id is removed in a finally block even on fatal error) makes this a no-op while a campaign's worker is alive. -
getDefaultParams
- Returns:
- the platform default campaign parameters
-
getCampaigns
- Returns:
- most recent campaigns, with their item aggregates — computed by a single grouped query for the whole list, never per campaign
-
getCampaign
public CleanupCampaign getCampaign(long campaignId) throws org.exoplatform.commons.exception.ObjectNotFoundException - Parameters:
campaignId- campaign identifier- Returns:
- the campaign with its item aggregates
- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when the campaign doesn't exist
-
createCampaign
Creates a campaign snapshotting the effective parameters, then launches its dry-run scan.- Parameters:
name- campaign name, mandatoryoverrides- partial parameter overrides, null fields defaulted- Returns:
- the created campaign
- Throws:
IllegalArgumentException- "cleanup.nameMandatory", "cleanup.nameTooLong" (pastMAX_NAME_LENGTHcharacters), "cleanup.invalidPeriodMonths" (must be >= 1), "cleanup.invalidMinFileSize" (must be >= 0), "cleanup.invalidGraceDays" (must be >= 0 — ZERO IS a valid grace period, elapsing at publication), "cleanup.invalidMaxVersionsPerFile" (must be >= 1)
-
deleteCampaign
public void deleteCampaign(long campaignId) throws org.exoplatform.commons.exception.ObjectNotFoundException Deletes a campaign and everything hanging off it: its item rows, its scan unit rows and its archived CSV.Allowed from
DELETABLE_STATESonly — a draft or a discarded simulation, where nothing was promised to a user and nothing was destroyed. Anything else answerscleanup.invalidState: a running campaign has a worker writing the very rows this would drop, a PUBLISHED or LOCKED one has told its users a date and collected their decisions, and a COMPLETED one is the record of an irreversible deletion (seeDELETABLE_STATES). Cancel first, then delete, is the path for the non-terminal ones.The JCR exemption mixins are NOT touched, and that is the important half: a user's "keep" is a standing decision on their own file, deliberately durable in JCR and deliberately outliving the campaign that collected it. Deleting a campaign must never silently un-keep a file — the next campaign has to see those decisions again.
- Parameters:
campaignId- campaign identifier- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when the campaign doesn't exist
-
deleteCampaignRowsTransactional
public void deleteCampaignRowsTransactional(long campaignId) Drops the report and scan unit rows of an already-deleted campaign, off the request thread.WHY IT IS NOT ON THE REQUEST THREAD: a SIMULATED campaign is the one carrying a FULL report — nothing has been purged yet, so its item table is at its maximum, hundreds of thousands of rows on the corpus this feature exists for. No REST call may depend on work whose duration grows with the corpus (a reverse proxy will cut it), which is why creation and execution answer 202 and hand their progress to CometD. Deleting is the third such operation, and the only one that used to answer synchronously.
The DELETE still answers 204 and not 202, because unlike those two nothing about the campaign is still pending: the campaign row is gone before the answer, so the resource really is deleted. What runs here is the collection of rows nothing can reach any more.
A JVM death in this window leaves those rows behind — visible to nobody, and holding the very space this feature reclaims — so they are swept at startup (
sweepOrphanRows()) rather than left to accumulate. That is also why the campaign row is deleted BEFORE these and not after: an orphaned item row is recoverable garbage, whereas a campaign whose report is half gone is a row an administrator can still publish.- Parameters:
campaignId- campaign identifier
-
sweepOrphanRows
public void sweepOrphanRows()Drops the item and scan unit rows whose campaign row no longer exists.Their only source is a JVM death in the middle of a delete (see
deleteCampaignRowsTransactional(long)), which is rare — and exactly why it must be swept rather than watched for: nothing else would ever notice rows that no query can reach, in a feature whose entire purpose is reclaiming space. Logged at WARN naming the campaigns, never silently. -
updateCampaign
public CleanupCampaign updateCampaign(long campaignId, String name, Integer graceDays) throws org.exoplatform.commons.exception.ObjectNotFoundException PARTIAL update of a campaign's editable attributes: its name, its grace period, or both. A null argument means 'leave that attribute unchanged', so the two fields are strictly independent — and their guards are per FIELD, not per request.The NAME is editable in ANY state, terminal ones included: it is pure METADATA, nothing keys off it and no lifecycle transition is involved, so correcting the label of an already-completed report is a legitimate need. Names are not unique (creation doesn't check either), so no uniqueness constraint is enforced. It goes through the very SAME validation as the creation path (
validateName(String)): the two cannot diverge, and neither can let a name longer than the NAME column reach the database.The GRACE PERIOD is state-guarded (
GRACE_EDITABLE_STATES) and validated by the very same bound check as the creation path (validateGraceDays(Integer)), so the two cannot diverge either — ZERO stays valid on both. Editing it can never invalidate the dry-run the administrator is reviewing: it is NOT a candidacy criterion, the scan selecting on the period, the minimum file size and the excluded paths alone.Once the campaign is PUBLISHED the grace period may only be EXTENDED, never shortened (architect decision W22) — see
applyGraceDays(org.exoplatform.addons.documents.cleanup.model.CleanupCampaign, java.lang.Integer). Before publication it is free in both directions, nothing having been promised yet.On a PUBLISHED campaign — and ONLY there — the lock date is recomputed from the PUBLICATION date, never from now: anchoring on now would slide the deadline forward on every save, so saving the same value twice would push it out twice. Everything downstream reads the lock date (the end users' remaining time included, through their summary's deadline), so that one field propagates the new deadline with NO second code path.
A recomputed deadline landing in the PAST is allowed and is not an error: it closes the review window immediately (
cleanup.reviewClosed) and the grace-deadline cron locks the campaign at its next tick — exactly what already happens with a zero grace period, tolerated for up to the cron period (seecheckReviewWindowOpen(CleanupCampaign)). This method deliberately performs NO transition of its own: the single PUBLISHED to LOCKED authority stayslockExpiredPublishedCampaign()and the manual execution trigger.Persisted as a TARGETED write of the two or three columns this operation owns (
CleanupCampaignStorage.updateEditableAttributes(long, java.lang.String, java.lang.Integer, java.lang.Long)), never as a whole-row save of the snapshot read above: the name is editable in EVERY state, so this write races the two schedule-driven writers of the same row — the workers' progress updates and the grace-deadline cron — and a read-modify-write would silently undo theirs.- Parameters:
campaignId- campaign identifiername- new campaign name, null to leave it unchanged — trimmed before being persistedgraceDays- new grace period in days, null to leave it unchanged — 0 is a MEANINGFUL value, not an absent one- Returns:
- the updated campaign, with its item aggregates
- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- "cleanup.campaignNotFound" when the campaign doesn't existIllegalArgumentException- "cleanup.nothingToUpdate" when both arguments are null, "cleanup.nameMandatory" when the name is blank, "cleanup.nameTooLong" pastMAX_NAME_LENGTHcharacters, "cleanup.invalidState" when the grace period is edited outsideGRACE_EDITABLE_STATES, "cleanup.invalidGraceDays" when it is negative, "cleanup.graceDaysCannotBeReduced" when it is LOWERED on a PUBLISHED campaign
-
publishCampaign
public CleanupCampaign publishCampaign(long campaignId) throws org.exoplatform.commons.exception.ObjectNotFoundException Publishes a SIMULATED campaign, starting its grace period. At most one campaign can be PUBLISHED/LOCKED/EXECUTING platform-wide.- Parameters:
campaignId- campaign identifier- Returns:
- the published campaign
- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when the campaign doesn't exist
-
executeCampaign
public CleanupCampaign executeCampaign(long campaignId) throws org.exoplatform.commons.exception.ObjectNotFoundException Triggers the batched purge of a LOCKED campaign. A PUBLISHED campaign whose grace deadline elapsed is first locked through the regular lifecycle transition (the same code path aslockExpiredPublishedCampaign(), including the observation listener unregistration), then executed; before the deadline the execution is rejected.- Parameters:
campaignId- campaign identifier- Returns:
- the campaign, now EXECUTING
- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when the campaign doesn't existIllegalArgumentException- "cleanup.graceNotElapsed" when the campaign is PUBLISHED and its grace deadline hasn't elapsed yet
-
retryCampaign
public CleanupCampaign retryCampaign(long campaignId) throws org.exoplatform.commons.exception.ObjectNotFoundException Re-attempts the RETRYABLE failed items of a COMPLETED campaign: no new scan, no new grace period, no new campaign. The requeued items go back to CANDIDATE and the campaign re-enters EXECUTING, so the very same worker processes them — and the worker needs NO change for that, its per-item revalidation against JCR immediately before deleting being what makes a requeued item safe (an item since exempted, modified or deleted is spared on its own).Lives HERE and not in
CleanupExecutionService, which owns the worker, because everything specific to a retry is business this Service already owns: the platform-wide single-active-campaign invariant and the verypublishLockthat makes its check-then-act atomic, plus the retryable allowlist. The execution part is then delegated toCleanupExecutionService.startExecution(long)verbatim — the requeue having just made the CANDIDATE count equal to the requeued count, its denominator reset, its lifecycle transition and its worker launch are exactly what a retry needs, and duplicating them here would be a second code path to keep in sync.A REFUSAL never starts a run: not COMPLETED, another campaign active, or nothing at all to retry all leave the campaign untouched.
- Parameters:
campaignId- campaign identifier- Returns:
- the campaign, now EXECUTING again, with its item aggregates
- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- "cleanup.campaignNotFound" when the campaign doesn't existIllegalArgumentException- "cleanup.invalidState" when the campaign isn't COMPLETED, "cleanup.campaignAlreadyActive" when another campaign is PUBLISHED/LOCKED/EXECUTING, "cleanup.noRetryableFailures" when no item qualifies
-
getCampaignFailures
public List<CleanupFailureGroup> getCampaignFailures(long campaignId) throws org.exoplatform.commons.exception.ObjectNotFoundException Grouped failures of a campaign: one entry per distinct failure message code, its SKIPPED item count, and whether a retry would re-attempt it — the flag read fromRETRYABLE_FAILURE_REASONS, so the console never has to hold its own copy of that rule.- Parameters:
campaignId- campaign identifier- Returns:
- the groups, EMPTY when the campaign has no failed item — or when the retention job already archived and purged its item rows, the grouped counts being computed over those rows and NOT snapshotted in the campaign summary
- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when the campaign doesn't exist
-
getCampaignScanFailures
public List<CleanupFailureGroup> getCampaignScanFailures(long campaignId) throws org.exoplatform.commons.exception.ObjectNotFoundException Grouped failures of a campaign's dry-run SCAN: one entry per distinct failure message code, with the number of SUBTREES that carry it. The unit-level twin ofgetCampaignFailures(long), answering the same shape so the console renders both through one block.A dry run used to report SIMULATED at 100% over a report silently missing whole subtrees — the only trace being a log line no administrator can read. This is that trace, made readable: whoever is about to publish the report sees how much of the tree it does NOT cover.
Delegates to
CleanupScanService, which owns the scan and its unit rows, after resolving the campaign here so an unknown id answers 404 exactly like every other campaign endpoint.- Parameters:
campaignId- campaign identifier- Returns:
- the groups, EMPTY when the campaign's scan covered the whole tree — which is the case of every campaign scanned before this bound existed
- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when the campaign doesn't exist
-
getCampaignScanUnitProgress
public CleanupScanUnitProgress getCampaignScanUnitProgress(long campaignId) throws org.exoplatform.commons.exception.ObjectNotFoundException Per-unit breakdown of a campaign's dry run, resolving the campaign here so an unknown id answers 404 like every other campaign endpoint.- Parameters:
campaignId- campaign identifier- Returns:
- the state counts, the deepest attempt spent, and the units in flight
- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when the campaign doesn't exist
-
cancelCampaign
public void cancelCampaign(long campaignId) throws org.exoplatform.commons.exception.ObjectNotFoundException Cancels a campaign from any non-terminal state.- Parameters:
campaignId- campaign identifier- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when the campaign doesn't exist
-
lockExpiredPublishedCampaign
public void lockExpiredPublishedCampaign()Scheduled-glue entry point: flips the PUBLISHED campaign to LOCKED once its grace deadline elapsed. -
keepItem
public void keepItem(long itemId, String username) throws org.exoplatform.commons.exception.ObjectNotFoundException, IllegalAccessException User decision to keep a candidate file: checks ownership (own file, or manager of the owning space), adds the exemption mixin and marks the item EXEMPTED.The ownership check runs FIRST, before any campaign-state or item-state check: probing item ids must never tell a non-owner whether an item exists nor which state its campaign is in.
- Parameters:
itemId- campaign item identifierusername- user requesting to keep the file- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when the item or its node doesn't existIllegalAccessException- when the user doesn't own the fileIllegalArgumentException- "cleanup.campaignNotPublished" when the campaign isn't PUBLISHED, "cleanup.reviewClosed" once the grace deadline elapsed, "cleanup.itemNotCandidate" when the item isn't decidableIllegalStateException- on a (possibly transient) JCR write failure, the item state left untouched so the keep can be retried
-
keepItems
Bulk variant ofkeepItem(long, String), continuing past individual failures and REPORTING them: a caller that answered a blanket success would tell the user their files are kept while none of them is.- Parameters:
itemIds- campaign item identifiersusername- user requesting to keep the files- Returns:
- per-item outcomes: how many succeeded, and which ones failed with which reason
-
unkeepItem
public void unkeepItem(long itemId, String username) throws org.exoplatform.commons.exception.ObjectNotFoundException, IllegalAccessException User decision to un-keep a previously kept file (undo ofkeepItem(long, String)): checks ownership (own file, or manager of the owning space), removes the exemption mixin, then revalidates the node through the shared revalidation mapping — still qualifying goes back to CANDIDATE, modified meanwhile to SPARED_BY_MODIFICATION, disappeared to GONE. Only allowed while the review window is open (campaign PUBLISHED and grace deadline not elapsed yet).The ownership check runs FIRST, before any campaign-state or item-state check: probing item ids must never tell a non-owner whether an item exists nor which state its campaign is in.
- Parameters:
itemId- campaign item identifierusername- user requesting to un-keep the file- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when the item or its node doesn't existIllegalAccessException- when the user doesn't own the fileIllegalArgumentException- "cleanup.reviewClosed" when the campaign isn't PUBLISHED anymore or its grace deadline elapsed, "cleanup.itemNotKept" when the item isn't EXEMPTEDIllegalStateException- on a (possibly transient) JCR write failure, the item state left untouched so the un-keep can be retried
-
unkeepItems
Bulk variant ofunkeepItem(long, String), continuing past individual failures and REPORTING them (seekeepItems(List, String)).- Parameters:
itemIds- campaign item identifiersusername- user requesting to un-keep the files- Returns:
- per-item outcomes: how many succeeded, and which ones failed with which reason
-
refreshCandidate
Freshness refresh (UX only, not the correctness guarantee), called by the JCR observation glue while a campaign is PUBLISHED. The event path is matched in BOTH directions so every JCR event shape refreshes the right items: a PROPERTY_CHANGED path (e.g./Users/.../file.pdf/jcr:content/jcr:data) matches the file item ABOVE it (ancestor-chain exact match), a folder removal/move matches the candidate items BELOW it (escaped prefix LIKE), and an exact node event matches the item itself.- Parameters:
itemPath- JCR event path (node or property path)eventType- JCR event type name
-
getCampaignItems
public org.springframework.data.domain.Page<CleanupCampaignItem> getCampaignItems(long campaignId, Long ownerIdentityId, CleanupItemState state, CleanupAction action, Long minSize, String search, org.springframework.data.domain.Pageable pageable) throws org.exoplatform.commons.exception.ObjectNotFoundException Campaign items with optional filters, for the admin console.- Parameters:
campaignId- campaign identifierownerIdentityId- optional owner filterstate- optional item state filteraction- optional action filterminSize- optional minimal content size filtersearch- optional case-insensitive search on the item path (covers the file name and its folders alike)pageable- page, size and sort- Returns:
- page of items
- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when the campaign doesn't exist
-
compareCampaigns
public CleanupComparison compareCampaigns(long baseCampaignId, long otherCampaignId) throws org.exoplatform.commons.exception.ObjectNotFoundException Delta between two campaigns' candidate sets, matched by node uuid. The three buckets are computed set-based by the database (three aggregate queries hitting the (CAMPAIGN_ID, NODE_UUID) unique index): neither campaign's candidate set is ever loaded in memory, so the endpoint stays bounded whatever the campaign size.- Parameters:
baseCampaignId- base campaign identifierotherCampaignId- compared campaign identifier- Returns:
- comparison counters and bytes
- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when either campaign doesn't exist
-
getMyItems
public org.springframework.data.domain.Page<CleanupCampaignItem> getMyItems(String username, String search, org.springframework.data.domain.Pageable pageable) throws org.exoplatform.commons.exception.ObjectNotFoundException Items of the currently relevant campaign owned by the user (own files and files of spaces they manage). The paging AND the ordering are the caller's (the REST layer validates the requested sort field against its allowlist and appends the stable tiebreaker), so this method never imposes an order of its own.- Parameters:
username- usersearch- optional case-insensitive search on the item path (covers the file name and its folders alike)pageable- page, size and sort- Returns:
- page of items
- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when no relevant campaign exists
-
getMyItemsSummary
public CleanupUserSummary getMyItemsSummary(String username) throws org.exoplatform.commons.exception.ObjectNotFoundException Per-user summary of the currently relevant campaign.- Parameters:
username- user- Returns:
- summary (candidates/kept during grace, outcome once completed)
- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when no relevant campaign exists, or when the relevant campaign does not concern this user at all — no candidate, nothing kept, nothing ever purged for them. The spec's REST table pins 404 on "no campaign concerns the user", and the header button hides on it (its fetch treats any error as 'nothing to review'), so an unaffected user is told so by the CONTRACT rather than by a 200 full of zeros the client has to interpret
-
checkArchiveAvailable
public void checkArchiveAvailable(long campaignId) throws org.exoplatform.commons.exception.ObjectNotFoundException Availability check of a campaign's CSV report, to run BEFORE a single byte is streamed: once the response body started flowing the HTTP status is already committed, so the not-found outcome has to be settled here. Only metadata is read (never the archive content).- Parameters:
campaignId- campaign identifier- Throws:
org.exoplatform.commons.exception.ObjectNotFoundException- when the campaign or its report doesn't exist anymore
-
writeArchiveCsv
Streams the CSV report of a campaign: generated page by page while item detail is retained (each page is flushed, so the first rows reach the client immediately and the whole report is never materialized in memory), copied from theFileServicearchive afterwards. Availability must have been settled bycheckArchiveAvailable(long)first.- Parameters:
campaignId- campaign identifieroutputStream- stream to write the CSV to, left open for the container to close- Throws:
IOException- when writing to the client stream fails
-
applyRetention
public void applyRetention()Scheduled-glue entry point: keeps item detail for the last N terminal campaigns only; older reports are archived as CSV (FileService) then their item detail is purged. Campaign headers are retained forever. -
getMaxScanThreads
public int getMaxScanThreads()- Returns:
- the highest reader-thread count a campaign may request, so the creation form can bound its input on the SAME number the server validates against — a form inventing its own bound is a form that disagrees with the server the day one of them changes
-