From b99c6acb1a05e99556a3cbed4d2ed7d3e2df5e20 Mon Sep 17 00:00:00 2001 From: Daniel Silva Date: Thu, 6 Aug 2026 15:32:43 +0200 Subject: [PATCH] fix(cache): add triggering user to cache flush log messages (#36830) Cache flush operations discarded the InitDataObject returned by WebResource.init(), so the authenticated user never reached the log line while other maintenance tasks (reindex, restarts) already logged it. - CacheResource: capture the user in the five flush endpoints and include user.getUserId() in the message; promote four flush logs from debug to info so the operations are visible at default log levels. - ViewCMSMaintenanceAction: resolve the portlet user (null-guarded) for the legacy Struts cache branch, which is the path the Maintenance tab's Flush Cache button actually posts to. - Register CacheResourceIntegrationTest in MainSuite3a; the class was never in a suite, so none of its tests ran in CI. Refs: #36830 Co-Authored-By: Claude Opus 5 (1M context) --- .../api/v1/system/cache/CacheResource.java | 40 ++++++++++--------- .../action/ViewCMSMaintenanceAction.java | 12 ++++-- .../src/test/java/com/dotcms/MainSuite3a.java | 2 + .../cache/CacheResourceIntegrationTest.java | 15 +++++++ 4 files changed, 48 insertions(+), 21 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/cache/CacheResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/cache/CacheResource.java index 0c4c8bd6b362..560c0c64e095 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/cache/CacheResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/system/cache/CacheResource.java @@ -17,6 +17,7 @@ import com.dotmarketing.util.MaintenanceUtil; import com.dotmarketing.util.PortletID; import com.google.common.annotations.VisibleForTesting; +import com.liferay.portal.model.User; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; @@ -295,15 +296,16 @@ public Response flushGroup(@Context final HttpServletRequest request, @PathParam("provider") final String provider, @PathParam("group") final String group) { - new WebResource.InitBuilder(webResource) + final User user = new WebResource.InitBuilder(webResource) .requestAndResponse(request, response) .requiredBackendUser(true) .requiredFrontendUser(false) .requiredPortlet(PortletID.MAINTENANCE.toString().toLowerCase()) - .rejectWhenNoUser(true).init(); + .rejectWhenNoUser(true).init().getUser(); - Logger.debug(this, ()-> "Deletes objects on cache providers, group: " + group - + ", provider = " + provider); + Logger.info(this, String.format( + "User '%s' is flushing cache group '%s' on provider '%s'", + user.getUserId(), group, provider)); this.getProvider(provider, group).remove(group); return Response.ok(new ResponseEntityView("flushed")).build(); @@ -327,15 +329,16 @@ public Response flushObject(@Context final HttpServletRequest request, @PathParam("group") final String group, @PathParam("id") final String id) { - new WebResource.InitBuilder(webResource) + final User user = new WebResource.InitBuilder(webResource) .requestAndResponse(request, response) .requiredBackendUser(true) .requiredFrontendUser(false) .requiredPortlet(PortletID.MAINTENANCE.toString().toLowerCase()) - .rejectWhenNoUser(true).init(); + .rejectWhenNoUser(true).init().getUser(); - Logger.debug(this, ()-> "Deletes object on cache providers, group: " + group - + ", provider = " + provider); + Logger.info(this, String.format( + "User '%s' is flushing cache object '%s' from group '%s' on provider '%s'", + user.getUserId(), id, group, provider)); this.getProvider(provider, group).remove(group, id); return Response.ok(new ResponseEntityView("flushed")).build(); @@ -369,14 +372,14 @@ public Response flushAll(@Context final HttpServletRequest request, @Context final HttpServletResponse response, @PathParam("provider") final String provider) { - new WebResource.InitBuilder(webResource) + final User user = new WebResource.InitBuilder(webResource) .requestAndResponse(request, response) .requiredBackendUser(true) .requiredFrontendUser(false) .requiredPortlet(PortletID.MAINTENANCE.toString().toLowerCase()) - .rejectWhenNoUser(true).init(); + .rejectWhenNoUser(true).init().getUser(); - Logger.debug(this, ()-> "Deletes all objects on cache provider = " + provider); + Logger.info(this, String.format("User '%s' is flushing all caches", user.getUserId())); cacheMaintenanceHelper.flushAllCaches(); return Response.ok(new ResponseEntityView("flushed all")).build(); @@ -505,21 +508,22 @@ public ResponseEntityStringView flushRegion( required = true, example = "Permission") @PathParam("regionName") final String regionName) { - new WebResource.InitBuilder(webResource) + final User user = new WebResource.InitBuilder(webResource) .requestAndResponse(request, response) .requiredBackendUser(true) .requiredFrontendUser(false) .requiredPortlet(PortletID.MAINTENANCE.toString().toLowerCase()) - .rejectWhenNoUser(true).init(); + .rejectWhenNoUser(true).init().getUser(); if ("all".equalsIgnoreCase(regionName)) { - Logger.info(this, "Flushing all caches"); + Logger.info(this, String.format("User '%s' is flushing all caches", user.getUserId())); cacheMaintenanceHelper.flushAllCaches(); return new ResponseEntityStringView("Flushed all caches"); } final String canonical = cacheMaintenanceHelper.flushRegion(regionName); - Logger.info(this, "Flushed cache region: " + canonical); + Logger.info(this, String.format("User '%s' flushed cache region '%s'", + user.getUserId(), canonical)); return new ResponseEntityStringView("Flushed " + canonical); } @@ -605,14 +609,14 @@ private CacheStatsView buildCacheStatsView() { public ResponseEntityStringView deleteMenuCache(@Context final HttpServletRequest request, @Context final HttpServletResponse response) { - new WebResource.InitBuilder(webResource) + final User user = new WebResource.InitBuilder(webResource) .requestAndResponse(request, response) .requiredBackendUser(true) .requiredFrontendUser(false) .requiredPortlet(PortletID.MAINTENANCE.toString().toLowerCase()) - .rejectWhenNoUser(true).init(); + .rejectWhenNoUser(true).init().getUser(); - Logger.debug(this, ()-> "Deleting menu cache"); + Logger.info(this, String.format("User '%s' is flushing the menu cache", user.getUserId())); MaintenanceUtil.deleteMenuCache(); return new ResponseEntityStringView("flushed menucache"); diff --git a/dotCMS/src/main/java/com/dotmarketing/portlets/cmsmaintenance/action/ViewCMSMaintenanceAction.java b/dotCMS/src/main/java/com/dotmarketing/portlets/cmsmaintenance/action/ViewCMSMaintenanceAction.java index f2cbc3d95c08..f59e6d94327c 100644 --- a/dotCMS/src/main/java/com/dotmarketing/portlets/cmsmaintenance/action/ViewCMSMaintenanceAction.java +++ b/dotCMS/src/main/java/com/dotmarketing/portlets/cmsmaintenance/action/ViewCMSMaintenanceAction.java @@ -37,6 +37,7 @@ import com.dotmarketing.util.ZipUtil; import com.dotmarketing.util.starter.ExportStarterUtil; import com.google.common.collect.ImmutableList; +import com.liferay.portal.model.User; import com.liferay.portlet.ActionResponseImpl; import com.liferay.util.FileUtil; import com.liferay.util.servlet.SessionMessages; @@ -130,6 +131,8 @@ public void processAction( //Manage all the cache Task if(cmd.equals("cache")){ + final User cacheUser = _getUser(req); + final String cacheUserId = null != cacheUser ? cacheUser.getUserId() : "unknown"; String cacheName = ccf.getCacheName(); if (cacheName.equals(com.dotmarketing.util.WebKeys.Cache.CACHE_CONTENTS_INDEX)) { @@ -165,7 +168,7 @@ public void processAction( } } else if (cacheName.equals(com.dotmarketing.util.WebKeys.Cache.CACHE_MENU_FILES)) { - Logger.info(this, "Deleting Menu Files"); + Logger.info(this, String.format("User '%s' is deleting Menu Files", cacheUserId)); _deleteMenusCache(); message = "message.cmsmaintenance.cache.flushmenucaches"; } else if (cacheName.equals("flushCache")) @@ -177,7 +180,9 @@ public void processAction( }catch (NullPointerException e) { isAllCachesFlush = true;//is a NPE is returned means it's cleaning all the caches } - final String msgLogger = isAllCachesFlush ? "Flushing All Caches" : "Flushing " + cacheToFlush +" Cache"; + final String msgLogger = isAllCachesFlush + ? String.format("User '%s' is flushing All Caches", cacheUserId) + : String.format("User '%s' is flushing %s Cache", cacheUserId, cacheToFlush); Logger.info(this, msgLogger); _flush(cacheToFlush); //Reloads PushPublishing Filters if all cache or system cache is flushed @@ -186,7 +191,8 @@ public void processAction( } message = isAllCachesFlush ? "message.cmsmaintenance.cache.flushallcache" : "message.cmsmaintenance.cache.flushcache"; } else { - Logger.info(this, "Flushing Live and Working File Cache"); + Logger.info(this, String.format( + "User '%s' is flushing Live and Working File Cache", cacheUserId)); _deleteFiles(com.dotmarketing.util.WebKeys.Cache.CACHE_LIVE_FILES); _deleteFiles(com.dotmarketing.util.WebKeys.Cache.CACHE_WORKING_FILES); diff --git a/dotcms-integration/src/test/java/com/dotcms/MainSuite3a.java b/dotcms-integration/src/test/java/com/dotcms/MainSuite3a.java index 740cfe36d921..0e647ff26604 100644 --- a/dotcms-integration/src/test/java/com/dotcms/MainSuite3a.java +++ b/dotcms-integration/src/test/java/com/dotcms/MainSuite3a.java @@ -13,6 +13,7 @@ import com.dotcms.rest.api.v1.drive.ContentDriveKeywordSearchTest; import com.dotcms.rest.api.v1.drive.ContentDriveWorkflowArchiveStepTest; import com.dotcms.rest.api.v1.drive.ContentDriveWorkflowFilterTest; +import com.dotcms.rest.api.v1.system.cache.CacheResourceIntegrationTest; import com.dotcms.security.apps.AppsAPIImplTest; import com.dotcms.telemetry.collectors.MetricTimeoutTest; import com.dotcms.telemetry.collectors.experiment.CountPagesWithAllEndedExperimentsMetricTypeTest; @@ -98,6 +99,7 @@ TreeFactoryTest.class, PublisherQueueJobTest.class, ContentToStringUtilTest.class, + CacheResourceIntegrationTest.class, }) public class MainSuite3a { diff --git a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/system/cache/CacheResourceIntegrationTest.java b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/system/cache/CacheResourceIntegrationTest.java index c52b7e4f4868..72a931ac43d5 100644 --- a/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/system/cache/CacheResourceIntegrationTest.java +++ b/dotcms-integration/src/test/java/com/dotcms/rest/api/v1/system/cache/CacheResourceIntegrationTest.java @@ -277,6 +277,21 @@ public void test_flushAll_existing_endpoint_returns_success() { assertEquals(200, result.getStatus()); } + /** + * Given: Authenticated admin user + * When: deleteMenuCache is called + * Then: Returns success — covers the newly added user dereference on this path + */ + @Test + public void test_deleteMenuCache_returns_success() { + + final ResponseEntityStringView result = + cacheResource.deleteMenuCache(mockAuthenticatedRequest(), mockResponse); + + assertNotNull(result); + assertEquals("flushed menucache", result.getEntity()); + } + // ========================================================================= // Helper // =========================================================================