Skip to content

maintenance portlet | Implement fix assets and clean orphan assets polling APIs #35191#35232

Open
hassandotcms wants to merge 6 commits intomainfrom
35200-maintenance-fix-assets-clean-assets
Open

maintenance portlet | Implement fix assets and clean orphan assets polling APIs #35191#35232
hassandotcms wants to merge 6 commits intomainfrom
35200-maintenance-fix-assets-clean-assets

Conversation

@hassandotcms
Copy link
Copy Markdown
Member

Summary

Add 4 new REST endpoints to MaintenanceResource, replacing legacy DWR fix assets and clean assets operations with modern REST APIs for the Maintenance portlet Tools tab.

New endpoints:

  • POST /api/v1/maintenance/_fixAssets — Runs all registered FixTask classes that check for and fix database inconsistencies. Executes synchronously and returns task results when complete.
  • GET /api/v1/maintenance/_fixAssets — Polls current fix assets task results. Used by the UI to check progress while the process is running.
  • POST /api/v1/maintenance/_cleanAssets — Starts a background thread that walks the assets directory structure, checks each directory against the contentlet database, and deletes orphan binary folders. Returns 409 Conflict if already running.
  • GET /api/v1/maintenance/_cleanAssets — Polls clean assets background process status (totalFiles, currentFiles, deleted, running, status).

Implementation details:

  • CleanAssetsStatusView (@Value.Immutable) with typed fields instead of BeanUtils.describe() string map
  • 409 Conflict guard on POST /_cleanAssets prevents IllegalThreadStateException from calling .start() on an already-running thread
  • FixTasksExecutor results returned as List<Map> (raw type from executor, cannot be typed further)
  • ResponseEntityCleanAssetsStatusView typed wrapper for Swagger documentation

Test plan

  • Integration tests pass: ./mvnw verify -pl :dotcms-integration -Dcoreit.test.skip=false -Dit.test=MaintenanceResourceIntegrationTest
  • POST /_fixAssets as admin returns 200 with task results or null entity
  • GET /_fixAssets returns current task results
  • POST /_cleanAssets starts process and returns initial status with running: true
  • GET /_cleanAssets returns current status with totalFiles, currentFiles, deleted, running, status fields
  • POST /_cleanAssets while already running returns 409 Conflict
  • Non-admin users get 403 on all endpoints

Closes #35200, Closes #35202

…s, and delete pushed assets #35191

Add three new endpoints to MaintenanceResource:
- POST /_searchAndReplace: database-wide find/replace across content tables
- DELETE /_oldVersions: drop versionable objects older than a given date
- DELETE /_pushedAssets: clear all push publishing history

Includes Validated forms, @Value.Immutable response views, full Swagger
annotations, and SecurityLogger entries for all destructive operations.
…ions, and pushed assets #35191

Tests cover:
- _searchAndReplace: admin success, non-admin rejection, null/empty form validation,
  empty replaceString allowed
- _oldVersions: admin success, non-admin rejection, missing date, invalid date formats
- _pushedAssets: admin success, non-admin rejection
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 7, 2026

Claude finished @hassandotcms's task in 3m 1s —— View job


PR Review

  • Gather context
  • Read changed files
  • Analyze for issues
  • Post review

Issues

1. POST /_fixAssets has no concurrency guard — shared state corruption risk

FixTasksExecutor is a singleton with a mutable instance field returnValue. At the top of execute(), it immediately does returnValue = new ArrayList<>(). Two concurrent POST requests will race on this: the second caller resets the list while the first is still populating it, silently discarding partial results. GET /_fixAssets polling during a concurrent run will return garbage.

POST /_cleanAssets has a 409 guard, but POST /_fixAssets has none despite running a comparably dangerous DB operation.

MaintenanceResource.java:752-765Fix this →

2. POST /_cleanAssets 409 check is not atomic

getInstance(true, true) creates a new thread+status if the current thread is dead. The isRunning() check happens after that. The running flag is set to true only inside run(), which executes asynchronously after thread.start(). There's a window where two concurrent POST requests both call getInstance(true, true), both see a not-alive thread, both get running=false, and both call start() on separate thread instances — bypassing the 409 entirely.

MaintenanceResource.java:843-854 / CleanAssetsThread.java:76-89

3. POST /_cleanAssets initial response shows running: false

thread.start() is called and then the status is immediately serialized. The thread hasn't had a chance to execute processStatus.setRunning(true) yet (that happens inside run() on the new thread). The first response to a client that just started the process will say running: false, which is wrong.

MaintenanceResource.java:853-855

4. FixTasksExecutor.execute() silently swallows all exceptions

In FixTasksExecutor.java:109-121, on exception it logs FATAL, throws DotDataException, then immediately catches it with an empty // TODO block. startFixAssets always returns HTTP 200 even when fix tasks completely fail. The caller cannot distinguish success from failure.

FixTasksExecutor.java:109-121 — This is pre-existing, but the new REST endpoint surfaces it as an observable correctness problem.

5. SecurityLogger missing from startFixAssets

All other destructive operations in this file (searchAndReplace, dropOldVersions, deletePushedAssets, startCleanAssets) call SecurityLogger.logInfo so the action appears in the security audit trail. startFixAssets only calls Logger.info.

MaintenanceResource.java:758-760Fix this →


Minor

  • form.checkValid() double call (MaintenanceResource.java:573): SearchAndReplaceForm's constructor already calls checkValid(). Calling it again in the endpoint is redundant.
  • null entity response: Both fix assets endpoints return new ResponseEntityView<>(results.isEmpty() ? null : results). Returning {"entity": null} before any tasks run is surprising for a polling endpoint — an empty list would be more consistent.
  • test_startCleanAssets_asAdmin_startsProcess doesn't assert running: true — it only asserts status != null. Given issue Add .gitignore #3 above (the initial response may show running: false), this test would pass even if the thread never started.

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 7, 2026

Rollback safety analysis complete. Safe To Rollback. Label applied.

@hassandotcms hassandotcms changed the base branch from 35199-maintenance-search-replace-old-versions-pushed-assets to main April 7, 2026 13:09
@github-actions github-actions bot added the Area : Backend PR changes Java/Maven backend code label Apr 7, 2026
@hassandotcms hassandotcms force-pushed the 35200-maintenance-fix-assets-clean-assets branch from 642aeae to cf2de86 Compare April 7, 2026 14:53
…ts with polling #35191

Add four new endpoints to MaintenanceResource:
- POST /_fixAssets: run all FixTask classes to fix DB inconsistencies
- GET /_fixAssets: poll fix assets task results
- POST /_cleanAssets: start background thread to clean orphan asset dirs
- GET /_cleanAssets: poll clean assets progress status

POST /_cleanAssets returns 409 Conflict if already running.
Includes @Value.Immutable CleanAssetsStatusView, full Swagger annotations,
and integration tests.
@hassandotcms hassandotcms force-pushed the 35200-maintenance-fix-assets-clean-assets branch from cf2de86 to 054bbe1 Compare April 8, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : Backend PR changes Java/Maven backend code

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[TASK] Implement clean assets endpoints [TASK] Implement fix assets endpoints

1 participant