diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/spans/ce/ApplicationSpanCE.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/spans/ce/ApplicationSpanCE.java index 6e27900e5920..57a8982e0e30 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/spans/ce/ApplicationSpanCE.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/constants/spans/ce/ApplicationSpanCE.java @@ -1,7 +1,10 @@ package com.appsmith.external.constants.spans.ce; +import static com.appsmith.external.constants.spans.ConsolidatedApiSpanNames.APPLICATION_ID_SPAN; import static com.appsmith.external.constants.spans.ConsolidatedApiSpanNames.CONSOLIDATED_API_PREFIX; public class ApplicationSpanCE { public static final String APPLICATION_FETCH_FROM_DB = CONSOLIDATED_API_PREFIX + "app_db"; + public static final String APPLICATION_ID_FETCH_REDIS_SPAN = APPLICATION_ID_SPAN + ".read_redis"; + public static final String APPLICATION_ID_UPDATE_REDIS_SPAN = APPLICATION_ID_SPAN + ".update_redis"; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceCEImpl.java index b04292a75406..50872df66db3 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/applications/base/ApplicationServiceCEImpl.java @@ -1037,7 +1037,9 @@ public Mono findByBaseIdBranchNameAndApplicationMode( ? applicationPermission.getReadPermission() : applicationPermission.getEditPermission(); - return findByBranchNameAndBaseApplicationId(branchName, defaultApplicationId, permissionForApplication); + return findByBranchNameAndBaseApplicationId(branchName, defaultApplicationId, permissionForApplication) + .name(APPLICATION_FETCH_FROM_DB) + .tap(Micrometer.observation(observationRegistry)); } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java index 41586161539a..12b787c4dfb8 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/newactions/base/NewActionServiceCEImpl.java @@ -1012,7 +1012,7 @@ public Flux getUnpublishedActions( Mono branchedPageMono = !StringUtils.hasLength(params.getFirst(FieldName.PAGE_ID)) ? Mono.just(new NewPage()) : newPageService.findByBranchNameAndBasePageId( - branchName, params.getFirst(FieldName.PAGE_ID), pagePermission.getReadPermission()); + branchName, params.getFirst(FieldName.PAGE_ID), pagePermission.getReadPermission(), null); Mono branchedApplicationMono = !StringUtils.hasLength(params.getFirst(FieldName.APPLICATION_ID)) ? Mono.just(new Application()) : applicationService.findByBranchNameAndBaseApplicationId( diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/newpages/base/NewPageServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/newpages/base/NewPageServiceCE.java index d8c1192de9ba..b669c91a4142 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/newpages/base/NewPageServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/newpages/base/NewPageServiceCE.java @@ -30,8 +30,6 @@ public interface NewPageServiceCE extends CrudService { Flux findNewPagesByApplicationId( String applicationId, AclPermission permission, List includeFields); - Mono findByIdAndBranchName(String id, String branchName); - Mono saveUnpublishedPage(PageDTO page); Mono createDefault(PageDTO object); @@ -71,7 +69,8 @@ Mono findByNameAndApplicationIdAndViewMode( Mono getNameByPageId(String pageId, boolean isPublishedName); - Mono findByBranchNameAndBasePageId(String branchName, String defaultPageId, AclPermission permission); + Mono findByBranchNameAndBasePageId( + String branchName, String defaultPageId, AclPermission permission, List projectedFieldNames); Mono findByBranchNameAndBasePageIdAndApplicationMode( String branchName, String basePageId, ApplicationMode mode); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/newpages/base/NewPageServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/newpages/base/NewPageServiceCEImpl.java index e8f418d39289..594e94d033e8 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/newpages/base/NewPageServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/newpages/base/NewPageServiceCEImpl.java @@ -136,11 +136,6 @@ public Flux findByApplicationId(String applicationId, AclPermission per return findNewPagesByApplicationId(applicationId, permission).flatMap(page -> getPageByViewMode(page, view)); } - @Override - public Mono findByIdAndBranchName(String id, String branchName) { - return this.findByBranchNameAndBasePageId(branchName, id, pagePermission.getReadPermission()); - } - @Override public Mono saveUnpublishedPage(PageDTO page) { @@ -511,19 +506,21 @@ public Mono getNameByPageId(String pageId, boolean isPublishedName) { } @Override - public Mono findByBranchNameAndBasePageId(String branchName, String basePageId, AclPermission permission) { + public Mono findByBranchNameAndBasePageId( + String branchName, String basePageId, AclPermission permission, List projectedFieldNames) { if (!StringUtils.hasText(basePageId)) { return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.PAGE_ID)); } else if (!StringUtils.hasText(branchName)) { - return this.findById(basePageId, permission) + return repository + .findById(basePageId, permission, projectedFieldNames) .name(GET_PAGE_WITHOUT_BRANCH) .tap(Micrometer.observation(observationRegistry)) .switchIfEmpty(Mono.error( new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, FieldName.PAGE, basePageId))); } return repository - .findPageByBranchNameAndBasePageId(branchName, basePageId, permission) + .findPageByBranchNameAndBasePageId(branchName, basePageId, permission, projectedFieldNames) .name(GET_PAGE_WITH_BRANCH) .tap(Micrometer.observation(observationRegistry)) .switchIfEmpty(Mono.error(new AppsmithException( @@ -541,7 +538,8 @@ public Mono findByBranchNameAndBasePageIdAndApplicationMode( permission = pagePermission.getReadPermission(); } - return this.findByBranchNameAndBasePageId(branchName, basePageId, permission) + return this.findByBranchNameAndBasePageId( + branchName, basePageId, permission, List.of(NewPage.Fields.id, NewPage.Fields.applicationId)) .name(getQualifiedSpanName(GET_PAGE, mode)) .tap(Micrometer.observation(observationRegistry)); } @@ -556,7 +554,7 @@ public Mono findBranchedPageId(String branchName, String basePageId, Acl return Mono.just(basePageId); } return repository - .findPageByBranchNameAndBasePageId(branchName, basePageId, permission) + .findPageByBranchNameAndBasePageId(branchName, basePageId, permission, null) .switchIfEmpty(Mono.error(new AppsmithException( AppsmithError.NO_RESOURCE_FOUND, FieldName.PAGE_ID, basePageId + ", " + branchName))) .map(NewPage::getId); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CacheableRepositoryHelperCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CacheableRepositoryHelperCE.java index 8bf2c469b9aa..fc66e5e2eeec 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CacheableRepositoryHelperCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CacheableRepositoryHelperCE.java @@ -4,6 +4,7 @@ import com.appsmith.server.domains.User; import reactor.core.publisher.Mono; +import java.util.List; import java.util.Set; public interface CacheableRepositoryHelperCE { @@ -23,4 +24,25 @@ public interface CacheableRepositoryHelperCE { Mono fetchDefaultTenant(String tenantId); Mono evictCachedTenant(String tenantId); + + /** + * Retrieves the base application ID from the cache based on the provided base page ID. + * + *

If the cache contains the ID for the specified {@code basePageId}, it is returned as a {@code Mono} containing the {@code baseApplicationId}. + * If the cache does not contain the ID (cache miss) and {@code baseApplicationId} is {@code null} or empty, an empty {@code Mono} is returned.

+ * + *

If {@code baseApplicationId} is not {@code null} or empty and a cache miss occurs, the cache will be updated with the provided {@code baseApplicationId}.

+ * + *

Note that calling this method with a {@code null} {@code baseApplicationId} on a cache miss will not update the cache. + * In this case, the method will return an empty {@code Mono}, and no cache update will occur.

+ * + * @param basePageId the identifier for the base page used as the cache key + * @param baseApplicationId the base application ID to be returned or used to update the cache if not {@code null} or empty + * @return a {@code Mono} containing the {@code baseApplicationId} if it is present in the cache or provided; otherwise, an empty {@code Mono} on a cache miss with a {@code null} or empty {@code baseApplicationId}. + * + *

On a cache miss, if {@code baseApplicationId} is provided, the cache will be updated with the new value after performing additional database operations to fetch the application document.

+ */ + Mono fetchBaseApplicationId(String basePageId, String baseApplicationId); + + Mono evictCachedBasePageIds(List basePageIds); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CacheableRepositoryHelperCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CacheableRepositoryHelperCEImpl.java index 330ae848910c..3cdcc4e8b087 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CacheableRepositoryHelperCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CacheableRepositoryHelperCEImpl.java @@ -21,9 +21,11 @@ import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import reactor.core.observability.micrometer.Micrometer; import reactor.core.publisher.Mono; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -41,6 +43,7 @@ public class CacheableRepositoryHelperCEImpl implements CacheableRepositoryHelpe private final ReactiveMongoOperations mongoOperations; private final InMemoryCacheableRepositoryHelper inMemoryCacheableRepositoryHelper; private final ObservationRegistry observationRegistry; + private static final String CACHE_DEFAULT_PAGE_ID_TO_DEFAULT_APPLICATION_ID = "pageIdToAppId"; @Cache(cacheName = "permissionGroupsForUser", key = "{#user.email + #user.tenantId}") @Override @@ -199,4 +202,16 @@ public Mono fetchDefaultTenant(String tenantId) { public Mono evictCachedTenant(String tenantId) { return Mono.empty().then(); } + + @Cache(cacheName = CACHE_DEFAULT_PAGE_ID_TO_DEFAULT_APPLICATION_ID, key = "{#basePageId}") + @Override + public Mono fetchBaseApplicationId(String basePageId, String baseApplicationId) { + return !StringUtils.hasText(baseApplicationId) ? Mono.empty() : Mono.just(baseApplicationId); + } + + @CacheEvict(cacheName = CACHE_DEFAULT_PAGE_ID_TO_DEFAULT_APPLICATION_ID, keys = "#basePageIds") + @Override + public Mono evictCachedBasePageIds(List basePageIds) { + return Mono.just(Boolean.TRUE); + } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCE.java index 43d30d5ea144..ca0beb98bec7 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCE.java @@ -12,6 +12,8 @@ public interface CustomNewPageRepositoryCE extends AppsmithRepository { + Mono findById(String id, AclPermission permission, List projectedFields); + Flux findByApplicationId(String applicationId, AclPermission aclPermission); Flux findByApplicationId(String applicationId, AclPermission aclPermission, List includeFields); @@ -30,7 +32,8 @@ Mono findByNameAndApplicationIdAndViewMode( Mono getNameByPageId(String pageId, boolean isPublishedName); - Mono findPageByBranchNameAndBasePageId(String branchName, String basePageId, AclPermission permission); + Mono findPageByBranchNameAndBasePageId( + String branchName, String basePageId, AclPermission permission, List projectedFieldNames); Flux findAllByApplicationIds(List branchedArtifactIds, List includedFields); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCEImpl.java index c1d59515075d..03b06fc065f2 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCEImpl.java @@ -40,6 +40,15 @@ public class CustomNewPageRepositoryCEImpl extends BaseAppsmithRepositoryImpl findById(String id, AclPermission permission, List projectedFields) { + return queryBuilder() + .criteria(Bridge.equal(NewPage.Fields.id, id)) + .permission(permission) + .fields(projectedFields) + .one(); + } + @Override public Flux findByApplicationId(String applicationId, AclPermission aclPermission) { return queryBuilder() @@ -161,7 +170,7 @@ public Mono getNameByPageId(String pageId, boolean isPublishedName) { @Override public Mono findPageByBranchNameAndBasePageId( - String branchName, String basePageId, AclPermission permission) { + String branchName, String basePageId, AclPermission permission, List projectedFieldNames) { final BridgeQuery q = // defaultPageIdCriteria @@ -177,6 +186,7 @@ public Mono findPageByBranchNameAndBasePageId( return queryBuilder() .criteria(q) .permission(permission) + .fields(projectedFieldNames) .one() .name(FETCH_PAGE_FROM_DB) .tap(Micrometer.observation(observationRegistry)); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java index 808a45405e4a..f31d8f50c3a4 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationPageServiceImpl.java @@ -13,6 +13,7 @@ import com.appsmith.server.newpages.base.NewPageService; import com.appsmith.server.repositories.ActionCollectionRepository; import com.appsmith.server.repositories.ApplicationRepository; +import com.appsmith.server.repositories.CacheableRepositoryHelper; import com.appsmith.server.repositories.DatasourceRepository; import com.appsmith.server.repositories.NewActionRepository; import com.appsmith.server.repositories.NewPageRepository; @@ -60,7 +61,8 @@ public ApplicationPageServiceImpl( DSLMigrationUtils dslMigrationUtils, ClonePageService actionClonePageService, ClonePageService actionCollectionClonePageService, - ObservationRegistry observationRegistry) { + ObservationRegistry observationRegistry, + CacheableRepositoryHelper cacheableRepositoryHelper) { super( workspaceService, applicationService, @@ -89,6 +91,7 @@ public ApplicationPageServiceImpl( dslMigrationUtils, actionClonePageService, actionCollectionClonePageService, - observationRegistry); + observationRegistry, + cacheableRepositoryHelper); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ConsolidatedAPIServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ConsolidatedAPIServiceImpl.java index f786ac90c1b9..9b0c48a44d0d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ConsolidatedAPIServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ConsolidatedAPIServiceImpl.java @@ -7,6 +7,7 @@ import com.appsmith.server.newactions.base.NewActionService; import com.appsmith.server.newpages.base.NewPageService; import com.appsmith.server.plugins.base.PluginService; +import com.appsmith.server.repositories.CacheableRepositoryHelper; import com.appsmith.server.services.ce_compatible.ConsolidatedAPIServiceCECompatibleImpl; import com.appsmith.server.themes.base.ThemeService; import io.micrometer.observation.ObservationRegistry; @@ -33,7 +34,8 @@ public ConsolidatedAPIServiceImpl( PluginService pluginService, DatasourceService datasourceService, MockDataService mockDataService, - ObservationRegistry observationRegistry) { + ObservationRegistry observationRegistry, + CacheableRepositoryHelper cacheableRepositoryHelper) { super( sessionUserService, userService, @@ -50,6 +52,7 @@ public ConsolidatedAPIServiceImpl( pluginService, datasourceService, mockDataService, - observationRegistry); + observationRegistry, + cacheableRepositoryHelper); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java index cc3cfd047f08..1536ba67a357 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java @@ -43,6 +43,7 @@ import com.appsmith.server.newpages.base.NewPageService; import com.appsmith.server.repositories.ActionCollectionRepository; import com.appsmith.server.repositories.ApplicationRepository; +import com.appsmith.server.repositories.CacheableRepositoryHelper; import com.appsmith.server.repositories.DatasourceRepository; import com.appsmith.server.repositories.NewActionRepository; import com.appsmith.server.repositories.NewPageRepository; @@ -128,6 +129,7 @@ public class ApplicationPageServiceCEImpl implements ApplicationPageServiceCE { private final ClonePageService actionClonePageService; private final ClonePageService actionCollectionClonePageService; private final ObservationRegistry observationRegistry; + private final CacheableRepositoryHelper cacheableRepositoryHelper; @Override public Mono createPage(PageDTO page) { @@ -311,7 +313,7 @@ public Mono getPageAndMigrateDslByBranchAndBasePageId( ApplicationMode applicationMode = viewMode ? ApplicationMode.PUBLISHED : ApplicationMode.EDIT; // Fetch the page with read permission in both editor and in viewer. return newPageService - .findByBranchNameAndBasePageId(branchName, defaultPageId, pagePermission.getReadPermission()) + .findByBranchNameAndBasePageId(branchName, defaultPageId, pagePermission.getReadPermission(), null) .flatMap(newPage -> getPageDTOAfterMigratingDSL(newPage, viewMode, migrateDsl) .name(getQualifiedSpanName(MIGRATE_DSL, applicationMode)) .tap(Micrometer.observation(observationRegistry))); @@ -1084,6 +1086,9 @@ protected Mono, ApplicationPublishingMetaDTO>> publishA Mono archivePageMono; + Mono evictDeletedDefaultPageIdsMono = + cacheableRepositoryHelper.evictCachedBasePageIds(new ArrayList<>(publishedPageIds)); + if (!publishedPageIds.isEmpty()) { archivePageMono = newPageService.archiveByIds(publishedPageIds); } else { @@ -1102,8 +1107,12 @@ protected Mono, ApplicationPublishingMetaDTO>> publishA newPageService.publishPages(editedPageIds, pagePermission.getEditPermission()); // Archive the deleted pages and save the application changes and then return the pages so that - // the pages can also be published - return Mono.when(archivePageMono, publishPagesMono, applicationService.save(application)) + // the pages can also be published; In addition invalidate the cache for the deleted page Ids + return Mono.when( + archivePageMono, + publishPagesMono, + applicationService.save(application), + evictDeletedDefaultPageIdsMono) .thenReturn(pages); }) .cache(); // caching as we'll need this to send analytics attributes after publishing the app diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ConsolidatedAPIServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ConsolidatedAPIServiceCEImpl.java index 7ec347e840bf..2cc9776c4744 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ConsolidatedAPIServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ConsolidatedAPIServiceCEImpl.java @@ -21,6 +21,7 @@ import com.appsmith.server.newactions.base.NewActionService; import com.appsmith.server.newpages.base.NewPageService; import com.appsmith.server.plugins.base.PluginService; +import com.appsmith.server.repositories.CacheableRepositoryHelper; import com.appsmith.server.services.ApplicationPageService; import com.appsmith.server.services.MockDataService; import com.appsmith.server.services.ProductAlertService; @@ -51,6 +52,8 @@ import java.util.stream.Collectors; import static com.appsmith.external.constants.PluginConstants.PLUGINS_THAT_ALLOW_QUERY_CREATION_WITHOUT_DATASOURCE; +import static com.appsmith.external.constants.spans.ApplicationSpan.APPLICATION_ID_FETCH_REDIS_SPAN; +import static com.appsmith.external.constants.spans.ApplicationSpan.APPLICATION_ID_UPDATE_REDIS_SPAN; import static com.appsmith.external.constants.spans.ConsolidatedApiSpanNames.ACTIONS_SPAN; import static com.appsmith.external.constants.spans.ConsolidatedApiSpanNames.ACTION_COLLECTIONS_SPAN; import static com.appsmith.external.constants.spans.ConsolidatedApiSpanNames.APPLICATION_ID_SPAN; @@ -99,6 +102,7 @@ public class ConsolidatedAPIServiceCEImpl implements ConsolidatedAPIServiceCE { private final DatasourceService datasourceService; private final MockDataService mockDataService; private final ObservationRegistry observationRegistry; + private final CacheableRepositoryHelper cacheableRepositoryHelper; ResponseDTO getSuccessResponse(T data) { return new ResponseDTO<>(HttpStatus.OK.value(), data, null); @@ -198,6 +202,19 @@ public Mono getConsolidatedInfoForPageLoad( /* Fetch default application id if not provided */ Mono branchedApplicationMonoCached; + Mono baseApplicationIdMono = Mono.just(""); + if (isViewMode) { + // Attempt to retrieve the application ID associated with the given base page ID from the cache. + baseApplicationIdMono = cacheableRepositoryHelper + .fetchBaseApplicationId(basePageId, baseApplicationId) + .switchIfEmpty(Mono.just("")) + .cast(String.class); + } + baseApplicationIdMono = baseApplicationIdMono + .name(getQualifiedSpanName(APPLICATION_ID_FETCH_REDIS_SPAN, mode)) + .tap(Micrometer.observation(observationRegistry)) + .cache(); + Mono branchedPageMonoCached = Mono.empty(); if (!isBlank(basePageId)) { branchedPageMonoCached = newPageService @@ -205,21 +222,40 @@ public Mono getConsolidatedInfoForPageLoad( .cache(); } - if (isBlank(baseApplicationId)) { - branchedApplicationMonoCached = branchedPageMonoCached - .map(NewPage::getApplicationId) - .flatMap(applicationId -> - applicationService.findByBranchedApplicationIdAndApplicationMode(applicationId, mode)) - .name(getQualifiedSpanName(APPLICATION_ID_SPAN, mode)) - .tap(Micrometer.observation(observationRegistry)) - .cache(); - } else { - branchedApplicationMonoCached = applicationService - .findByBaseIdBranchNameAndApplicationMode(baseApplicationId, branchName, mode) - .name(getQualifiedSpanName(APPLICATION_ID_SPAN, mode)) - .tap(Micrometer.observation(observationRegistry)) - .cache(); - } + branchedApplicationMonoCached = baseApplicationIdMono.flatMap(cachedBaseApplicationId -> { + if (!StringUtils.hasText(cachedBaseApplicationId)) { + // Handle empty or null baseApplicationId + return newPageService + .findByBranchNameAndBasePageIdAndApplicationMode(branchName, basePageId, mode) + .flatMap(branchedPage -> + // Use the application ID to find the complete application details. + applicationService + .findByBranchedApplicationIdAndApplicationMode( + branchedPage.getApplicationId(), mode) + .flatMap(application -> { + if (isViewMode) { + // Update the cache with the new application’s base ID for future + // queries. + return cacheableRepositoryHelper + .fetchBaseApplicationId(basePageId, application.getBaseId()) + .thenReturn(application) + .name(getQualifiedSpanName( + APPLICATION_ID_UPDATE_REDIS_SPAN, mode)) + .tap(Micrometer.observation(observationRegistry)); + } + return Mono.just(application); + })); + } else { + // Handle non-empty baseApplicationId + return applicationService.findByBaseIdBranchNameAndApplicationMode( + cachedBaseApplicationId, branchName, mode); + } + }); + + branchedApplicationMonoCached = branchedApplicationMonoCached + .name(getQualifiedSpanName(APPLICATION_ID_SPAN, mode)) + .tap(Micrometer.observation(observationRegistry)) + .cache(); Mono> pagesFromCurrentApplicationMonoCached = branchedApplicationMonoCached .flatMap(branchedApplication -> diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CurlImporterServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CurlImporterServiceCEImpl.java index 0a75c2f9d7bb..d4a473ae650e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CurlImporterServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CurlImporterServiceCEImpl.java @@ -107,7 +107,8 @@ public Mono importAction( protected Mono getBranchedContextId(CreatorContextType contextType, String contextId, String branchName) { return newPageService - .findByBranchNameAndBasePageId(branchName, contextId, pagePermission.getActionCreatePermission()) + .findByBranchNameAndBasePageId( + branchName, contextId, pagePermission.getActionCreatePermission(), List.of(NewPage.Fields.id)) .map(NewPage::getId); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/ConsolidatedAPIServiceCECompatibleImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/ConsolidatedAPIServiceCECompatibleImpl.java index c41efc869570..c7cc66cc4360 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/ConsolidatedAPIServiceCECompatibleImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce_compatible/ConsolidatedAPIServiceCECompatibleImpl.java @@ -7,6 +7,7 @@ import com.appsmith.server.newactions.base.NewActionService; import com.appsmith.server.newpages.base.NewPageService; import com.appsmith.server.plugins.base.PluginService; +import com.appsmith.server.repositories.CacheableRepositoryHelper; import com.appsmith.server.services.ApplicationPageService; import com.appsmith.server.services.MockDataService; import com.appsmith.server.services.ProductAlertService; @@ -36,7 +37,8 @@ public ConsolidatedAPIServiceCECompatibleImpl( PluginService pluginService, DatasourceService datasourceService, MockDataService mockDataService, - ObservationRegistry observationRegistry) { + ObservationRegistry observationRegistry, + CacheableRepositoryHelper cacheableRepositoryHelper) { super( sessionUserService, userService, @@ -53,6 +55,7 @@ public ConsolidatedAPIServiceCECompatibleImpl( pluginService, datasourceService, mockDataService, - observationRegistry); + observationRegistry, + cacheableRepositoryHelper); } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryTest.java index 6cff70087a01..71da2c690722 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryTest.java @@ -101,7 +101,7 @@ void publishPages_WhenIdMatches_Published() { @Test void findPageWithoutBranchName() { StepVerifier.create(newPageRepository.findPageByBranchNameAndBasePageId( - null, "pageId", AclPermission.PAGE_CREATE_PAGE_ACTIONS)) + null, "pageId", AclPermission.PAGE_CREATE_PAGE_ACTIONS, null)) .verifyComplete(); } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceImplTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceImplTest.java index 716779b16535..43b0d91a0850 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceImplTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceImplTest.java @@ -200,7 +200,8 @@ public void testCreateCollection_withRepeatedActionName_throwsError() throws IOE final NewPage newPage = objectMapper.convertValue(jsonNode.get("newPage"), NewPage.class); Mockito.when(newPageService.findById(Mockito.any(), Mockito.any())) .thenReturn(Mono.just(newPage)); - Mockito.when(newPageService.findByBranchNameAndBasePageId(Mockito.any(), Mockito.any(), Mockito.any())) + Mockito.when(newPageService.findByBranchNameAndBasePageId( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())) .thenReturn(Mono.just(newPage)); Mockito.when(refactoringService.isNameAllowed(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())) .thenReturn(Mono.just(false)); @@ -233,7 +234,8 @@ public void testCreateCollection_createActionFailure_returnsWithIncompleteCollec Mockito.when(newPageService.findById(Mockito.any(), Mockito.any())) .thenReturn(Mono.just(newPage)); - Mockito.when(newPageService.findByBranchNameAndBasePageId(Mockito.any(), Mockito.any(), Mockito.any())) + Mockito.when(newPageService.findByBranchNameAndBasePageId( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())) .thenReturn(Mono.just(newPage)); Mockito.when(refactoringService.isNameAllowed(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())) .thenReturn(Mono.just(true)); @@ -290,7 +292,8 @@ public void testCreateCollection_validCollection_returnsPopulatedCollection() th Mockito.when(newPageService.findById(Mockito.any(), Mockito.any())) .thenReturn(Mono.just(newPage)); - Mockito.when(newPageService.findByBranchNameAndBasePageId(Mockito.any(), Mockito.any(), Mockito.any())) + Mockito.when(newPageService.findByBranchNameAndBasePageId( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())) .thenReturn(Mono.just(newPage)); Mockito.when(refactoringService.isNameAllowed(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())) .thenReturn(Mono.just(true)); @@ -385,7 +388,8 @@ public void testUpdateUnpublishedActionCollection_withInvalidId_throwsError() th Mockito.when(actionCollectionRepository.findById(Mockito.anyString(), Mockito.any())) .thenReturn(Mono.empty()); - Mockito.when(newPageService.findByBranchNameAndBasePageId(Mockito.any(), Mockito.any(), Mockito.any())) + Mockito.when(newPageService.findByBranchNameAndBasePageId( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())) .thenReturn(Mono.just(newPage)); Mockito.when(newPageService.findById(Mockito.any(), Mockito.any())) diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ConsolidatedAPIServiceImplTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ConsolidatedAPIServiceImplTest.java index c030539e9ce7..cddb33847c5b 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ConsolidatedAPIServiceImplTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ConsolidatedAPIServiceImplTest.java @@ -225,7 +225,7 @@ public void testPageLoadResponseForViewMode() { mockNewPage.setId("mockPageId"); doReturn(Mono.just(mockNewPage)) .when(spyNewPageService) - .findByBranchNameAndBasePageId(anyString(), anyString(), any()); + .findByBranchNameAndBasePageId(anyString(), anyString(), any(), any()); doReturn(Mono.just(List.of(mockNewPage))) .when(spyApplicationPageService) @@ -268,7 +268,7 @@ public void testPageLoadResponseForViewMode() { Mono consolidatedInfoForPageLoad = consolidatedAPIService.getConsolidatedInfoForPageLoad( - "pageId", null, "branch", ApplicationMode.PUBLISHED); + "pageId123", null, "branch", ApplicationMode.PUBLISHED); StepVerifier.create(consolidatedInfoForPageLoad) .assertNext(consolidatedAPIResponseDTO -> { assertNotNull(consolidatedAPIResponseDTO.getPublishedActions()); @@ -416,7 +416,7 @@ public void testPageLoadResponseForEditMode() { mockNewPage.setApplicationId("mockApplicationId"); doReturn(Mono.just(mockNewPage)) .when(spyNewPageService) - .findByBranchNameAndBasePageId(anyString(), anyString(), any()); + .findByBranchNameAndBasePageId(anyString(), anyString(), any(), any()); doReturn(Mono.just(List.of(mockNewPage))) .when(spyApplicationPageService) @@ -721,7 +721,7 @@ public void testErrorResponseWhenAnonymousUserAccessPrivateApp() { when(mockProductAlertService.getSingleApplicableMessage()) .thenReturn(Mono.just(List.of(sampleProductAlertResponseDTO))); - when(mockNewPageRepository.findPageByBranchNameAndBasePageId(anyString(), anyString(), any())) + when(mockNewPageRepository.findPageByBranchNameAndBasePageId(anyString(), anyString(), any(), any())) .thenReturn(Mono.empty()); doReturn(Mono.empty()) .when(spyApplicationRepository) diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java index fc702317864c..3ec766f804c9 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/PageServiceTest.java @@ -863,7 +863,7 @@ public void clonePage_whenPageCloned_defaultIdsRetained() { final Mono pageMono = applicationPageService .clonePage(page.getId()) .flatMap(pageDTO -> - newPageService.findByBranchNameAndBasePageId(branchName, pageDTO.getId(), MANAGE_PAGES)) + newPageService.findByBranchNameAndBasePageId(branchName, pageDTO.getId(), MANAGE_PAGES, null)) .cache(); Mono> actionsMono = pageMono.flatMapMany( diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationServiceCETest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationServiceCETest.java index 001452bb0588..f71b19257321 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationServiceCETest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationServiceCETest.java @@ -19,6 +19,7 @@ import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.ApplicationDetail; +import com.appsmith.server.domains.ApplicationMode; import com.appsmith.server.domains.ApplicationPage; import com.appsmith.server.domains.Asset; import com.appsmith.server.domains.CustomJSLib; @@ -62,6 +63,7 @@ import com.appsmith.server.repositories.PermissionGroupRepository; import com.appsmith.server.repositories.PluginRepository; import com.appsmith.server.repositories.UserRepository; +import com.appsmith.server.services.ConsolidatedAPIService; import com.appsmith.server.services.LayoutActionService; import com.appsmith.server.services.LayoutCollectionService; import com.appsmith.server.services.PermissionGroupService; @@ -124,6 +126,7 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import static com.appsmith.server.acl.AclPermission.CONNECT_TO_GIT; @@ -289,6 +292,9 @@ public class ApplicationServiceCETest { @Autowired private AssetRepository assetRepository; + @Autowired + private ConsolidatedAPIService consolidatedAPIService; + private Mono runAs(Mono input, User user) { log.info("Running as user: {}", user.getEmail()); return input.contextWrite((ctx) -> { @@ -1710,7 +1716,7 @@ public void cloneApplication_applicationWithGitMetadata_success() { Mono> srcNewPageListMono = Flux.fromIterable(gitConnectedApp.getPages()) .flatMap(applicationPage -> newPageService.findByBranchNameAndBasePageId( - branchName, applicationPage.getDefaultPageId(), READ_PAGES)) + branchName, applicationPage.getDefaultPageId(), READ_PAGES, null)) .collectList(); StepVerifier.create(Mono.zip(clonedNewPageListMono, srcNewPageListMono)) @@ -4392,4 +4398,126 @@ public void findByWorkspaceIdAndDefaultApplicationsInRecentlyUsedOrder_invalidWo }) .verify(); } + + @Test + @WithUserDetails(value = "api_user") + public void testCacheEviction_whenPagesDeletedInEditModeFollowedByAppPublish_shouldInvalidateCache() { + // Step 1: Initialize the test application and page identifiers + Application testApplication = new Application(); + String appName = "ApplicationServiceTest Publish Application Delete Page"; + testApplication.setName(appName); + AtomicReference basePageId1Ref = new AtomicReference<>(); + AtomicReference basePageId2Ref = new AtomicReference<>(); + + // Step 2: Create an application with a page and publish it + Mono applicationMono = applicationPageService + .createApplication(testApplication, workspaceId) + .flatMap(application -> { + // Step 2.1: Create a new page and set default layout + PageDTO page = new PageDTO(); + page.setName("New Page"); + page.setApplicationId(application.getId()); + Layout defaultLayout = newPageService.createDefaultLayout(); + List layouts = new ArrayList<>(); + layouts.add(defaultLayout); + page.setLayouts(layouts); + + // Step 2.2: Create and clone the page, then publish the application + return applicationPageService + .createPage(page) + .flatMap(page1 -> { + basePageId1Ref.set(page1.getBaseId()); + return applicationPageService + .clonePage(page1.getId()) + .flatMap(clonedPage -> { + basePageId2Ref.set(clonedPage.getId()); + return applicationPageService.publish(page1.getApplicationId(), true); + }); + }) + .then(applicationService.findById(application.getId(), MANAGE_APPLICATIONS)); + }) + .cache(); + + // Step 3: Fetch the new page and verify its existence + PageDTO newPage = applicationMono + .flatMap(application -> newPageService + .findByNameAndApplicationIdAndViewMode("New Page", application.getId(), READ_PAGES, false) + .switchIfEmpty(Mono.error(new AppsmithException(AppsmithError.NO_RESOURCE_FOUND, "page")))) + .block(); + + // Step 4: Assert that page IDs are not null + assertThat(basePageId1Ref.get()).isNotNull(); + assertThat(basePageId2Ref.get()).isNotNull(); + + // Step 5: Delete the pages in edit mode + applicationPageService.deleteUnpublishedPage(basePageId1Ref.get()).block(); + applicationPageService.deleteUnpublishedPage(basePageId2Ref.get()).block(); + + // Step 6: Verify basePageId1 is not cached before calling the consolidated API + String cachedBaseAppId1 = cacheableRepositoryHelper + .fetchBaseApplicationId(basePageId1Ref.get(), null) + .block(); + assertThat(cachedBaseAppId1).isNull(); + + // Step 7: Call the consolidated API to force cache update + consolidatedAPIService + .getConsolidatedInfoForPageLoad(basePageId1Ref.get(), null, null, ApplicationMode.PUBLISHED) + .block(); + + // Step 8: Verify basePageId1 is now cached after the consolidated API call + cachedBaseAppId1 = cacheableRepositoryHelper + .fetchBaseApplicationId(basePageId1Ref.get(), null) + .block(); + assertThat(cachedBaseAppId1).isNotNull(); + + // Step 9: Verify basePageId2 is not cached before calling the consolidated API + String cachedBaseAppId2 = cacheableRepositoryHelper + .fetchBaseApplicationId(basePageId2Ref.get(), null) + .block(); + assertThat(cachedBaseAppId2).isNull(); + + // Step 10: Call the consolidated API to force cache update for basePageId2 + consolidatedAPIService + .getConsolidatedInfoForPageLoad(basePageId2Ref.get(), null, null, ApplicationMode.PUBLISHED) + .block(); + + // Step 11: Verify basePageId2 is now cached after the consolidated API call + cachedBaseAppId2 = cacheableRepositoryHelper + .fetchBaseApplicationId(basePageId2Ref.get(), null) + .block(); + assertThat(cachedBaseAppId2).isNotNull(); + + // Step 12: Verify the application pages after deletion and publishing + ApplicationPage applicationPage = new ApplicationPage(); + applicationPage.setId(newPage.getId()); + applicationPage.setIsDefault(false); + applicationPage.setDefaultPageId(newPage.getId()); + + StepVerifier.create(applicationService.findById(newPage.getApplicationId(), MANAGE_APPLICATIONS)) + .assertNext(editedApplication -> { + // Step 12.1: Check the published pages and edited pages + List publishedPages = editedApplication.getPublishedPages(); + assertThat(publishedPages).size().isEqualTo(3); + assertThat(publishedPages).containsAnyOf(applicationPage); + + List editedApplicationPages = editedApplication.getPages(); + assertThat(editedApplicationPages).hasSize(1); + assertThat(editedApplicationPages).doesNotContain(applicationPage); + }) + .verifyComplete(); + + // Step 13: Publish the application again + applicationPageService.publish(newPage.getApplicationId(), true).block(); + + // Step 14: Verify that the cache entries for deleted pages are evicted + cachedBaseAppId1 = cacheableRepositoryHelper + .fetchBaseApplicationId(basePageId1Ref.get(), null) + .block(); + assertThat(cachedBaseAppId1).isNull(); + + cachedBaseAppId2 = cacheableRepositoryHelper + .fetchBaseApplicationId(basePageId2Ref.get(), null) + .block(); + assertThat(cachedBaseAppId2).isNull(); + } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/CreateDBTablePageSolutionTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/CreateDBTablePageSolutionTests.java index ae1af27a4609..5ebba1803e6a 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/CreateDBTablePageSolutionTests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/CreateDBTablePageSolutionTests.java @@ -367,7 +367,7 @@ public void createPage_withValidBranch_validDefaultIds() { .flatMap(savedPage -> solution.createPageFromDBTable(savedPage.getId(), resource, testDefaultEnvironmentId)) .flatMap(crudPageResponseDTO -> newPageService.findByBranchNameAndBasePageId( - gitData.getBranchName(), crudPageResponseDTO.getPage().getId(), READ_PAGES)); + gitData.getBranchName(), crudPageResponseDTO.getPage().getId(), READ_PAGES, null)); StepVerifier.create(resultMono.zipWhen(newPage1 -> getActions(newPage1.getId()))) .assertNext(tuple -> { diff --git a/app/server/reactive-caching/src/main/java/com/appsmith/caching/annotations/CacheEvict.java b/app/server/reactive-caching/src/main/java/com/appsmith/caching/annotations/CacheEvict.java index 001aaa0b648a..7d307df03d8c 100644 --- a/app/server/reactive-caching/src/main/java/com/appsmith/caching/annotations/CacheEvict.java +++ b/app/server/reactive-caching/src/main/java/com/appsmith/caching/annotations/CacheEvict.java @@ -23,6 +23,13 @@ */ String key() default ""; + /** + * Array of keys for which cache entries should be evicted. + * Can be used to specify multiple keys for bulk eviction. + * If empty, a single key can be derived from the method arguments. + */ + String[] keys() default {}; + /** * Whether to evict all keys for a given cache name. */ diff --git a/app/server/reactive-caching/src/main/java/com/appsmith/caching/aspects/CacheAspect.java b/app/server/reactive-caching/src/main/java/com/appsmith/caching/aspects/CacheAspect.java index 0ef5e9410e8a..7e489e64c5d9 100644 --- a/app/server/reactive-caching/src/main/java/com/appsmith/caching/aspects/CacheAspect.java +++ b/app/server/reactive-caching/src/main/java/com/appsmith/caching/aspects/CacheAspect.java @@ -11,6 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.interceptor.SimpleKey; import org.springframework.expression.EvaluationContext; +import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; @@ -19,6 +20,7 @@ import reactor.core.publisher.Mono; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.List; /** @@ -201,6 +203,7 @@ public Object cacheEvict(ProceedingJoinPoint joinPoint) throws Throwable { CacheEvict annotation = method.getAnnotation(CacheEvict.class); String cacheName = annotation.cacheName(); boolean all = annotation.all(); + String[] keys = annotation.keys(); // Get the array of keys Class returnType = method.getReturnType(); if (!returnType.isAssignableFrom(Mono.class)) { @@ -212,10 +215,55 @@ public Object cacheEvict(ProceedingJoinPoint joinPoint) throws Throwable { return cacheManager.evictAll(cacheName).then((Mono) joinPoint.proceed()); } + // Evict multiple keys + if (keys.length > 0) { // If there are specific keys, evict those + // Create a Flux from the array of keys and map each key to a Mono of eviction + + // Create the expression parser and evaluation context + ExpressionParser parser = new SpelExpressionParser(); + StandardEvaluationContext context = new StandardEvaluationContext(); + + // Bind method arguments to the context + String[] parameterNames = signature.getParameterNames(); + List> evictionMonos = new ArrayList<>(); + for (int i = 0; i < joinPoint.getArgs().length; i++) { + context.setVariable(parameterNames[i], joinPoint.getArgs()[i]); + } + + // Evaluate each key expression + for (String keyExpression : keys) { + // Parse and evaluate the expression + Expression expression = parser.parseExpression(keyExpression); + + Object keyObj = expression.getValue(context); + + // Handle case where the key value is a List + if (keyObj instanceof List) { + List keyList = (List) keyObj; + for (Object key : keyList) { + if (key != null) { + evictionMonos.add(cacheManager.evict(cacheName, key.toString())); + } + } + } else { + // Single key handling + if (keyObj != null) { + evictionMonos.add(cacheManager.evict(cacheName, keyObj.toString())); + } + } + } + return Flux.fromIterable(evictionMonos) + .flatMap(voidMono -> voidMono) + .collectList() + .then((Mono) joinPoint.proceed()); + } + + // Evict single key // derive key String[] parameterNames = signature.getParameterNames(); Object[] args = joinPoint.getArgs(); String key = deriveKey(annotation.key(), parameterNames, args); + // Evict key from the cache then call the original method return cacheManager.evict(cacheName, key).then((Mono) joinPoint.proceed()); }