diff --git a/.project b/.project
index f1a6caf..801ad63 100644
--- a/.project
+++ b/.project
@@ -1,23 +1,34 @@
- sanecleanup
-
-
-
-
-
- org.eclipse.jdt.core.javabuilder
-
-
-
-
- org.springframework.ide.eclipse.core.springbuilder
-
-
-
-
-
- org.springframework.ide.eclipse.core.springnature
- org.eclipse.jdt.core.javanature
-
-
\ No newline at end of file
+ sanecleanup
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.springframework.ide.eclipse.core.springbuilder
+
+
+
+
+
+ org.springframework.ide.eclipse.core.springnature
+ org.eclipse.jdt.core.javanature
+
+
+
+ 1606724818477
+
+ 30
+
+ org.eclipse.core.resources.regexFilterMatcher
+ node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__
+
+
+
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..fcb3bfe
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,52 @@
+# Changelog
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+## [2.0.0] - 2021-03-23
+
+### Changed
+
+- Cleanup jobs / retention rules are now imported on-demand based on the extensions the project uses.
+
+### Added
+
+- Custom retention cronjob to replace CMS Version [Garbage Collection][versiongc].\
+ The ootb garbage collection mechanism is over-engineered and should be a regular cronjob.
+
+- Retention rules and jobs for the promotion engine, based on [Top 10 Recommendations for Improving the Performance of your Commerce Cloud Promotion Engine][top10]
+
+[versiongc]: https://help.sap.com/viewer/9d346683b0084da2938be8a285c0c27a/2011/en-US/9089116335ac4f4d8708e0c5516531e3.html
+[top10]: https://www.sap.com/cxworks/article/538808299/top_10_recommendations_for_improving_the_performance_of_your_commerce_cloud_promotion_engine
+
+## [1.0.1] - 22020-12-09
+
+### Added
+
+- Bulk cleanup cronjob for log files - useful for a one-time cleanup before the retention
+ job for job logs is enabled
+
+## [1.0.0] - 2020-11-26
+
+Initial release
+
+### Added
+
+- Cleanup for:
+
+ - CronJobs
+ - CronJob Histories
+ - Lob Logs / Log Files
+ - Impex Media
+ - HTTP Sessions
+ - Business Processes
+ - Carts
+
+[Unreleased]: https://github.com/sap-commerce-tools/sanecleanup/compare/v2.0.0...HEAD
+[2.0.0]: https://github.com/sap-commerce-tools/sanecleanup/compare/v1.0.1...v2.0.0
+[1.0.1]: https://github.com/sap-commerce-tools/sanecleanup/compare/v1.0.0...v1.0.1
+[1.0.0]: https://github.com/sap-commerce-tools/sanecleanup/releases/tag/v1.0.0
+
diff --git a/README.md b/README.md
index 0c84cb5..9258fd7 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,14 @@
# sanecleanup
-![SAP Commerce 1811+](https://img.shields.io/badge/Commerce-1811+-0051ab?logo=SAP)
+![SAP Commerce 1811+](https://img.shields.io/badge/Commerce-2005+-0051ab?logo=SAP)
Sensible defaults for data retention and cleanup for SAP Commerce, based on my CX Works article [Data Maintenance and Cleanup][article]
-1. Download the repository as zip file
+1. Download the latest release
1. Unpack to `hybris/bin/custom`
-1. **Review and adapt the retention rules** and cronjobs defined in `sanecleanup/resources/impex/*.impex`\
-1. If possible, disable storing of saved values / change history too! ([help.sap.com][stored], further recommendations in my [article][stored-kill])
+1. If possible, disable saved values / change history (ref. [help.sap.com][stored], further recommendations in my [article][stored-kill])
1. Add extension to your `localextensions.xml`
````xml
@@ -17,13 +16,18 @@ Sensible defaults for data retention and cleanup for SAP Commerce, based on my C
````
1. Build and deploy.\
- (The rules will be imported during system update)
+ (The rules will be automatically imported during system update)
**Warning**\
-The first run of `cronJobLogCleanupCronJob` will take a _very_ long time, if you have never removed any cronjob log files (type `LogFile`).\
+The very first execution of the retention cron jobs will take a while, depending on how long your poject
+is already live and if you have cleaned up anything in the past.
+
+Consider performing a [one-time cleanup][one] before adding the extension / enabling the retention rules.
+
+Especially the first run of `cronJobLogCleanupCronJob` will take a _very_ long time, if you have never removed any cronjob log files (type `LogFile`).\
Please consider importing and executing the script job defined in [bulkdelete-cronjoblogs.impex](resources/impex/bulkdelete-cronjoblogs.impex) **before** you set up the automated cleanup!\
-The job will remove all log files except the five most recent ones per CronJob.
-(Disclaimer: the script was tested on MS SQL / Azure SQL. It is not guaranteed to work for other Databases)
+The job will remove all log files except the five most recent logs per CronJob.
+(Disclaimer: the script was tested on MS SQL / Azure SQL. It is not guaranteed to work for other databases)
## Support
diff --git a/extensioninfo.xml b/extensioninfo.xml
index ee74d67..31c9ff1 100644
--- a/extensioninfo.xml
+++ b/extensioninfo.xml
@@ -4,6 +4,8 @@
+
+
diff --git a/project.properties b/project.properties
index 8749403..41b4bc5 100644
--- a/project.properties
+++ b/project.properties
@@ -1,3 +1,9 @@
sanecleanup.application-context=sanecleanup-spring.xml
+# disable change history entries / "Last Changes"
# hmc.storing.modifiedvalues.size=0
+
+# replaced with mpern.sap.cleanup.cms2.CMSVersionGCPerformable
+version.gc.enabled=false
+
+sanecleanup.jobs.sessionlanguage=en
diff --git a/resources/impex/bulkdelete-cronjoblogs.impex b/resources/impex/bulkdelete-cronjoblogs.impex
index 8642ae8..920120a 100644
--- a/resources/impex/bulkdelete-cronjoblogs.impex
+++ b/resources/impex/bulkdelete-cronjoblogs.impex
@@ -7,6 +7,7 @@ import de.hybris.platform.cronjob.enums.CronJobStatus
import de.hybris.platform.cronjob.enums.CronJobResult
// all log files EXCEPT the five most recent logs per cronjob
+// warning: query uses MS SQL dialact for partioning the logs per cronjob
def QUERY = '''
SELECT t.pk
FROM
diff --git a/resources/impex/essentialdata-cleanup-businessprocess.impex b/resources/impex/essentialdata-cleanup-businessprocess.impex
deleted file mode 100644
index c04fcb7..0000000
--- a/resources/impex/essentialdata-cleanup-businessprocess.impex
+++ /dev/null
@@ -1,14 +0,0 @@
-$twoWeeks = 1209600
-INSERT FlexibleSearchRetentionRule; code[unique = true] ; searchQuery; retentionTimeSeconds; actionReference ;
- ; businessProcessRule ; "SELECT {p:pk}, {p:itemtype}
- FROM {BusinessProcess AS p JOIN ProcessState AS s ON {p:state} = {s:pk} }
- WHERE
- {s:code} in ('SUCCEEDED') AND
- {p:modifiedTime} < ?CALC_RETIREMENT_TIME" ; $twoWeeks ; basicRemoveCleanupAction ;
-INSERT RetentionJob; code[unique = true] ; retentionRule(code) ; batchSize
- ; businessProcessCleanupJob ; businessProcessRule ; 1000
-INSERT CronJob; code[unique = true] ; job(code) ; sessionLanguage(isoCode)[default = en]
- ; businessProcessCleanupCronJob ; businessProcessCleanupJob ;
-INSERT Trigger; cronJob(code)[unique = true] ; cronExpression
-# every day at midnight
- ; businessProcessCleanupCronJob ; 0 0 0 * * ?
diff --git a/resources/impex/essentialdata-cleanup-cronjobhistory.impex b/resources/impex/essentialdata-cleanup-cronjobhistory.impex
deleted file mode 100644
index b5c650d..0000000
--- a/resources/impex/essentialdata-cleanup-cronjobhistory.impex
+++ /dev/null
@@ -1,21 +0,0 @@
-# ---------------------------------------------------------------------------
-# Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved.
-# ---------------------------------------------------------------------------
-# ref. https://launchpad.support.sap.com/#/notes/2848601
-# using INSERT ensures we import this only once, and only if the item(s) don't exist
-
-# RETENTION RULE for cleaning up cron job history items
-INSERT FlexibleSearchRetentionRule;code[unique=true];searchQuery;actionReference;
-; cronJobHistoryRetentionRule; SELECT {h1:PK}, {h1:itemtype} FROM {CronJobHistory as h1} WHERE {h1:creationtime} < (SELECT max FROM ({{SELECT max({h2:creationtime}) as max, {h2:cronjob} as cronjob FROM {CronJobHistory as h2} GROUP BY {h2:cronjob} }}) temptable where cronjob = {h1:cronjob}); basicRemoveCleanupAction;
-
-# JOB
-INSERT RetentionJob; code[unique=true]; retentionRule(code); batchSize
-; cronJobHistoryRetentionJob; cronJobHistoryRetentionRule; 1000
-
-# CRON JOB
-INSERT CronJob;code[unique=true]; job(code); sessionLanguage(isoCode)[default=en]
-; cronJobHistoryRetentionCronJob; cronJobHistoryRetentionJob;
-
-INSERT Trigger; cronJob(code)[unique=true]; cronExpression
-# every your
-;cronJobHistoryRetentionCronJob; 0 0 0/1 * * ? ;
diff --git a/resources/impex/essentialdata-cleanup-cronjoblogs.impex b/resources/impex/essentialdata-cleanup-cronjoblogs.impex
deleted file mode 100644
index 986a71d..0000000
--- a/resources/impex/essentialdata-cleanup-cronjoblogs.impex
+++ /dev/null
@@ -1,5 +0,0 @@
-INSERT CronJob; code[unique = true] ; job(code) ; sessionLanguage(isoCode)[default = en]
- ; cronJobLogCleanupCronJob ; cleanUpLogsJobPerformable ;
-INSERT Trigger; cronJob(code)[unique = true]; cronExpression
-# every hour
- ; cronJobLogCleanupCronJob ; 0 0 0/1 * * ?
diff --git a/resources/impex/essentialdata-cleanup-cronjobs.impex b/resources/impex/essentialdata-cleanup-cronjobs.impex
deleted file mode 100644
index 24ad576..0000000
--- a/resources/impex/essentialdata-cleanup-cronjobs.impex
+++ /dev/null
@@ -1,16 +0,0 @@
-$twoWeeks = 1209600
-INSERT FlexibleSearchRetentionRule; code[unique = true]; searchQuery; retentionTimeSeconds; actionReference ;
- ; cronJobRule ; "select {c:pk}, {c:itemType}
- from {CronJob as c join ComposedType as t on {c:itemtype} = {t:pk} left join Trigger as trg on {trg:cronjob} = {c:pk} }
- where
- {trg:pk} is null and
- {c:code} like '00______%' and
- {t:code} in ( 'ImpExImportCronJob', 'CatalogVersionSyncCronJob', 'SolrIndexerCronJob' ) and
- {c:endTime} < ?CALC_RETIREMENT_TIME" ; $twoWeeks ; basicRemoveCleanupAction ;
-INSERT RetentionJob; code[unique = true]; retentionRule(code); batchSize
- ; cronJobCleanupJob ; cronJobRule ; 1000
-INSERT CronJob; code[unique = true] ; job(code) ; sessionLanguage(isoCode)[default = en]
- ; cronJobCleanupCronJob ; cronJobCleanupJob ;
-INSERT Trigger; cronJob(code)[unique = true]; cronExpression
-# every day at midnight
- ; cronJobCleanupCronJob ; 0 0 0 * * ?
diff --git a/resources/impex/essentialdata-cleanup-impex.impex b/resources/impex/essentialdata-cleanup-impex.impex
deleted file mode 100644
index bb90df6..0000000
--- a/resources/impex/essentialdata-cleanup-impex.impex
+++ /dev/null
@@ -1,14 +0,0 @@
-$twoWeeks = 1209600
-INSERT FlexibleSearchRetentionRule; code[unique = true]; searchQuery; retentionTimeSeconds; actionReference ;
- ; impexMediaRule ; "select {i:pk}, {i:itemtype}
- from {ImpexMedia as i}
- where
- {i:code} like '00______' and
- {i:modifiedTime} < ?CALC_RETIREMENT_TIME" ; $twoWeeks ; basicRemoveCleanupAction ;
-INSERT RetentionJob; code[unique = true] ; retentionRule(code); batchSize
- ; impexMediaCleanupJob ; impexMediaRule ; 1000
-INSERT CronJob; code[unique = true] ; job(code) ; sessionLanguage(isoCode)[default = en]
- ; impexMediaCleanupCronJob ; impexMediaCleanupJob ;
-INSERT Trigger; cronJob(code)[unique = true]; cronExpression
-# every day at midnight
- ; impexMediaCleanupCronJob ; 0 0 0 * * ?
\ No newline at end of file
diff --git a/resources/impex/essentialdata-cleanup-oldcarts.impex b/resources/impex/essentialdata-cleanup-oldcarts.impex
deleted file mode 100644
index 97609a6..0000000
--- a/resources/impex/essentialdata-cleanup-oldcarts.impex
+++ /dev/null
@@ -1,31 +0,0 @@
-# based on de.hybris.platform.commercewebservices.core.cronjob.OldCartRemovalJob
-# and de.hybris.platform.commerceservices.order.dao.impl.DefaultCommerceCartDao.getCartsForRemovalForSiteAndUser
-#
-# - cleanup anonymous carts after two weeks for *every* site (excluding saved carts)
-# - cleanup carts of registered users after four weeks for *every* site (excluding saved carts)
-$twoWeeks = 1209600
-$fourWeeks = 2419200
-INSERT FlexibleSearchRetentionRule; code[unique = true]; searchQuery; retentionTimeSeconds; actionReference ;
- ; cartRule ; "select {c:pk}, {c:itemtype}
- from {Cart as c LEFT JOIN User as u on {c:user} = {u:pk}}
- where
- {c:saveTime} IS NULL AND
- {u:uid} <> 'anonymous' AND
- {c:modifiedTime} < ?CALC_RETIREMENT_TIME" ; $fourWeeks ; basicRemoveCleanupAction ;
- ; anonymousCartRule ; "select {c:pk}, {c:itemtype}
- from {Cart as c LEFT JOIN User as u on {c:user} = {u:pk}}
- where
- {c:saveTime} IS NULL AND
- ( {u:uid} = 'anonymous' OR {u:uid} IS NULL ) AND
- {c:modifiedTime} < ?CALC_RETIREMENT_TIME" ; $twoWeeks ; basicRemoveCleanupAction ;
-
-INSERT RetentionJob; code[unique = true] ; retentionRule(code); batchSize
- ; cartCleanupJob ; cartRule ; 1000
- ; anonymousCartCleanupJob ; anonymousCartRule ; 1000
-INSERT CronJob; code[unique = true] ; job(code) ; sessionLanguage(isoCode)[default = en]
- ; cartCleanupCronJob ; cartCleanupJob ;
- ; anonymousCartCleanupCronJob ; anonymousCartCleanupJob ;
-INSERT Trigger; cronJob(code)[unique = true]; cronExpression
-# every day at midnight
- ; cartCleanupCronJob ; 0 0 0 * * ?
- ; anonymousCartCleanupCronJob ; 0 0 0 * * ?
diff --git a/resources/impex/essentialdata-clenup-httpsession.impex b/resources/impex/essentialdata-clenup-httpsession.impex
deleted file mode 100644
index 27b9acd..0000000
--- a/resources/impex/essentialdata-clenup-httpsession.impex
+++ /dev/null
@@ -1,13 +0,0 @@
-$oneDay = 86400
-INSERT FlexibleSearchRetentionRule; code[unique = true] ; searchQuery; retentionTimeSeconds; actionReference ;
- ; storedHttpSessionRule ; "select {s:pk}, {s:itemtype}
- from {StoredHttpSession as s}
- where
- {s:modifiedTime} < ?CALC_RETIREMENT_TIME" ; $oneDay ; basicRemoveCleanupAction ;
-INSERT RetentionJob; code[unique = true] ; retentionRule(code) ; batchSize
- ; storedHttpSessionCleanupJob ; storedHttpSessionRule ; 1000
-INSERT CronJob; code[unique = true] ; job(code) ; sessionLanguage(isoCode)[default = en]
- ; storedHttpSessionCleanupCronJob ; storedHttpSessionCleanupJob ;
-INSERT Trigger; cronJob(code)[unique = true] ; cronExpression
-# every 30 minutes
- ; storedHttpSessionCleanupCronJob ; 0 0/30 * * * ?
diff --git a/resources/impex/essentialdata-jobs.impex b/resources/impex/essentialdata-jobs.impex
new file mode 100644
index 0000000..01ee46e
--- /dev/null
+++ b/resources/impex/essentialdata-jobs.impex
@@ -0,0 +1,3 @@
+
+INSERT_UPDATE ServicelayerJob;code[unique=true];springId;
+;cmsVersionGCJob;cmsVersionGCPerformable;
diff --git a/resources/impex/sanecleanup/cms2/001-optimized-versiongc.impex b/resources/impex/sanecleanup/cms2/001-optimized-versiongc.impex
new file mode 100644
index 0000000..6c663ca
--- /dev/null
+++ b/resources/impex/sanecleanup/cms2/001-optimized-versiongc.impex
@@ -0,0 +1,11 @@
+
+# Import config properties into impex macros
+UPDATE GenericItem[processor=de.hybris.platform.commerceservices.impex.impl.ConfigPropertyImportProcessor];pk[unique=true]
+$sessionLanguage=$config-sanecleanup.jobs.sessionlanguage
+$cronExpression=$config-version.gc.cron
+
+INSERT_UPDATE CronJob;code[unique=true];job(code);sessionLanguage(isoCode)[default = $sessionLanguage]
+;cmsVersionGCCronJob;cmsVersionGCJob;
+
+INSERT_UPDATE Trigger;cronjob(code)[unique=true];cronExpression
+;cmsVersionGCCronJob; $cronExpression
diff --git a/resources/impex/sanecleanup/commerceservices/001-cleanup-oldcarts.impex b/resources/impex/sanecleanup/commerceservices/001-cleanup-oldcarts.impex
new file mode 100644
index 0000000..f1821a1
--- /dev/null
+++ b/resources/impex/sanecleanup/commerceservices/001-cleanup-oldcarts.impex
@@ -0,0 +1,36 @@
+# based on de.hybris.platform.commercewebservices.core.cronjob.OldCartRemovalJob
+# and de.hybris.platform.commerceservices.order.dao.impl.DefaultCommerceCartDao.getCartsForRemovalForSiteAndUser
+#
+# - cleanup anonymous carts after two weeks for *every* site (excluding saved carts)
+# - cleanup carts of registered users after four weeks for *every* site (excluding saved carts)
+
+# Import config properties into impex macros
+UPDATE GenericItem[processor=de.hybris.platform.commerceservices.impex.impl.ConfigPropertyImportProcessor];pk[unique=true]
+$sessionLanguage=$config-sanecleanup.jobs.sessionlanguage
+
+$twoWeeks = 1209600
+$fourWeeks = 2419200
+INSERT_UPDATE FlexibleSearchRetentionRule; code[unique = true]; searchQuery; retentionTimeSeconds; actionReference ;
+ ; cartRule ; "
+ SELECT {c:pk}, {c:itemtype}
+ FROM { Cart AS c LEFT JOIN User AS u ON {c:user} = {u:pk} }
+ WHERE {c:saveTime} IS NULL
+ AND {u:uid} <> 'anonymous'
+ AND {c:modifiedTime} < ?CALC_RETIREMENT_TIME" ; $fourWeeks ; basicRemoveCleanupAction ;
+ ; anonymousCartRule ; "
+ SELECT {c:pk}, {c:itemtype}
+ FROM {Cart AS c LEFT JOIN User AS u ON {c:user} = {u:pk}}
+ WHERE {c:saveTime} IS NULL
+ AND ( {u:uid} = 'anonymous' OR {u:uid} IS NULL )
+ AND {c:modifiedTime} < ?CALC_RETIREMENT_TIME" ; $twoWeeks ; basicRemoveCleanupAction ;
+
+INSERT_UPDATE RetentionJob; code[unique = true] ; retentionRule(code); batchSize
+ ; cartCleanupJob ; cartRule ; 1000
+ ; anonymousCartCleanupJob ; anonymousCartRule ; 1000
+INSERT_UPDATE CronJob; code[unique = true] ; job(code) ; sessionLanguage(isoCode)[default = $sessionLanguage]
+ ; cartCleanupCronJob ; cartCleanupJob ;
+ ; anonymousCartCleanupCronJob ; anonymousCartCleanupJob ;
+INSERT_UPDATE Trigger; cronJob(code)[unique = true]; cronExpression
+# every day at midnight
+ ; cartCleanupCronJob ; 0 0 0 * * ?
+ ; anonymousCartCleanupCronJob ; 0 0 0 * * ?
diff --git a/resources/impex/sanecleanup/core/001-cleanup-httpsession.impex b/resources/impex/sanecleanup/core/001-cleanup-httpsession.impex
new file mode 100644
index 0000000..1a61a95
--- /dev/null
+++ b/resources/impex/sanecleanup/core/001-cleanup-httpsession.impex
@@ -0,0 +1,18 @@
+# delete all stored sessions one day after there last update
+
+# Import config properties into impex macros
+UPDATE GenericItem[processor = de.hybris.platform.commerceservices.impex.impl.ConfigPropertyImportProcessor]; pk[unique = true]
+$sessionLanguage = $config-sanecleanup.jobs.sessionlanguage
+
+$oneDay = 86400
+INSERT_UPDATE FlexibleSearchRetentionRule; code[unique = true] ; searchQuery; retentionTimeSeconds; actionReference ;
+ ; storedHttpSessionRule ; "select {s:pk}, {s:itemtype}
+ from {StoredHttpSession as s}
+ where {s:modifiedTime} < ?CALC_RETIREMENT_TIME" ; $oneDay ; basicRemoveCleanupAction ;
+INSERT_UPDATE RetentionJob; code[unique = true] ; retentionRule(code) ; batchSize
+ ; storedHttpSessionCleanupJob ; storedHttpSessionRule ; 1000
+INSERT_UPDATE CronJob; code[unique = true] ; job(code) ; sessionLanguage(isoCode)[default = $sessionLanguage]
+ ; storedHttpSessionCleanupCronJob ; storedHttpSessionCleanupJob ;
+INSERT_UPDATE Trigger; cronJob(code)[unique = true] ; cronExpression
+# every 30 minutes
+ ; storedHttpSessionCleanupCronJob ; 0 0/30 * * * ?
diff --git a/resources/impex/sanecleanup/impex/001_cleanup-impex.impex b/resources/impex/sanecleanup/impex/001_cleanup-impex.impex
new file mode 100644
index 0000000..b326e40
--- /dev/null
+++ b/resources/impex/sanecleanup/impex/001_cleanup-impex.impex
@@ -0,0 +1,14 @@
+$twoWeeks = 1209600
+INSERT_UPDATE FlexibleSearchRetentionRule; code[unique = true]; searchQuery; retentionTimeSeconds; actionReference ;
+ ; impexMediaRule ; "
+ SELECT {i:pk}, {i:itemtype}
+ FROM {ImpexMedia AS i}
+ WHERE {i:code} LIKE '00______'
+ AND {i:modifiedTime} < ?CALC_RETIREMENT_TIME" ; $twoWeeks ; basicRemoveCleanupAction ;
+INSERT_UPDATE RetentionJob; code[unique = true] ; retentionRule(code); batchSize
+ ; impexMediaCleanupJob ; impexMediaRule ; 1000
+INSERT_UPDATE CronJob; code[unique = true] ; job(code) ; sessionLanguage(isoCode)[default = en]
+ ; impexMediaCleanupCronJob ; impexMediaCleanupJob ;
+INSERT_UPDATE Trigger; cronJob(code)[unique = true]; cronExpression
+# every day at midnight
+ ; impexMediaCleanupCronJob ; 0 0 0 * * ?
\ No newline at end of file
diff --git a/resources/impex/sanecleanup/platformservices/001-enable-cronjoblogs-cleanup.impex b/resources/impex/sanecleanup/platformservices/001-enable-cronjoblogs-cleanup.impex
new file mode 100644
index 0000000..78dfada
--- /dev/null
+++ b/resources/impex/sanecleanup/platformservices/001-enable-cronjoblogs-cleanup.impex
@@ -0,0 +1,13 @@
+# enable ootb cronjob logs cleanup
+# if you have never enabled this before, have a look at bulkdelete-cronjoblogs.impex for a cronjob that you can use to
+# delete a large number of stale job logs
+
+# Import config properties into impex macros
+UPDATE GenericItem[processor=de.hybris.platform.commerceservices.impex.impl.ConfigPropertyImportProcessor];pk[unique=true]
+$sessionLanguage=$config-sanecleanup.jobs.sessionlanguage
+
+INSERT_UPDATE CronJob; code[unique = true] ; job(code) ;queryCount; sessionLanguage(isoCode)[default = $sessionLanguage]
+ ; cronJobLogCleanupCronJob ; cleanUpLogsJobPerformable ; 2147483647 ;
+INSERT_UPDATE Trigger; cronJob(code)[unique = true]; cronExpression
+# every hour
+ ; cronJobLogCleanupCronJob ; 0 0 0/1 * * ?
diff --git a/resources/impex/sanecleanup/processing/001-cleanup-cronjobhistory.impex b/resources/impex/sanecleanup/processing/001-cleanup-cronjobhistory.impex
new file mode 100644
index 0000000..fe3d4b5
--- /dev/null
+++ b/resources/impex/sanecleanup/processing/001-cleanup-cronjobhistory.impex
@@ -0,0 +1,88 @@
+# ---------------------------------------------------------------------------
+# Copyright (c) 2019 SAP SE or an SAP affiliate company. All rights reserved.
+# ---------------------------------------------------------------------------
+# ref. https://launchpad.support.sap.com/#/notes/2848601
+
+# Import config properties into impex macros
+UPDATE GenericItem[processor=de.hybris.platform.commerceservices.impex.impl.ConfigPropertyImportProcessor];pk[unique=true]
+$sessionLanguage=$config-sanecleanup.jobs.sessionlanguage
+
+# RETENTION RULE for cleaning up cron job history items
+INSERT_UPDATE FlexibleSearchRetentionRule;code[unique=true];searchQuery;actionReference;
+"#% beforeEach:
+import de.hybris.platform.core.Registry;
+import de.hybris.platform.cronjob.model.CronJobModel;
+CronJobModel cronJob;
+try
+{
+ cronJob = Registry.getApplicationContext().getBean(""cronJobService"").getCronJob(""cronJobHistoryRetentionCronJob"");
+}
+catch (Exception e)
+{
+ cronJob = null;
+}
+if (cronJob != null)
+{
+ line.clear();
+}"
+; cronJobHistoryRetentionRule; SELECT {h1:PK}, {h1:itemtype} FROM {CronJobHistory as h1} WHERE {h1:creationtime} < (SELECT max FROM ({{SELECT max({h2:creationtime}) as max, {h2:cronjob} as cronjob FROM {CronJobHistory as h2} GROUP BY {h2:cronjob} }}) temptable where cronjob = {h1:cronjob}); basicRemoveCleanupAction;
+
+# JOB
+INSERT_UPDATE RetentionJob; code[unique=true]; retentionRule(code); batchSize
+"#% beforeEach:
+import de.hybris.platform.core.Registry;
+import de.hybris.platform.cronjob.model.CronJobModel;
+CronJobModel cronJob;
+try
+{
+ cronJob = Registry.getApplicationContext().getBean(""cronJobService"").getCronJob(""cronJobHistoryRetentionCronJob"");
+}
+catch (Exception e)
+{
+ cronJob = null;
+}
+if (cronJob != null)
+{
+ line.clear();
+}"
+; cronJobHistoryRetentionJob; cronJobHistoryRetentionRule; 1000
+
+# CRON JOB
+INSERT_UPDATE CronJob;code[unique=true]; job(code); sessionLanguage(isoCode)[default=$sessionLanguage]
+"#% beforeEach:
+import de.hybris.platform.core.Registry;
+import de.hybris.platform.cronjob.model.CronJobModel;
+CronJobModel cronJob;
+try
+{
+ cronJob = Registry.getApplicationContext().getBean(""cronJobService"").getCronJob(""cronJobHistoryRetentionCronJob"");
+}
+catch (Exception e)
+{
+ cronJob = null;
+}
+if (cronJob != null)
+{
+ line.clear();
+}"
+; cronJobHistoryRetentionCronJob; cronJobHistoryRetentionJob;
+
+INSERT_UPDATE Trigger; cronJob(code)[unique=true]; cronExpression
+# every hour
+"#% beforeEach:
+import de.hybris.platform.core.Registry;
+import de.hybris.platform.cronjob.model.CronJobModel;
+CronJobModel cronJob;
+try
+{
+ cronJob = Registry.getApplicationContext().getBean(""cronJobService"").getCronJob(""cronJobHistoryRetentionCronJob"");
+}
+catch (Exception e)
+{
+ cronJob = null;
+}
+if (cronJob != null)
+{
+ line.clear();
+}"
+;cronJobHistoryRetentionCronJob; 0 0 0/1 * * ? ;
diff --git a/resources/impex/sanecleanup/processing/002-cleanup-cronjobs.impex b/resources/impex/sanecleanup/processing/002-cleanup-cronjobs.impex
new file mode 100644
index 0000000..6620fae
--- /dev/null
+++ b/resources/impex/sanecleanup/processing/002-cleanup-cronjobs.impex
@@ -0,0 +1,32 @@
+# Delete all generated jobs without a trigger
+
+# Import config properties into impex macros
+UPDATE GenericItem[processor = de.hybris.platform.commerceservices.impex.impl.ConfigPropertyImportProcessor]; pk[unique = true]
+$sessionLanguage = $config-sanecleanup.jobs.sessionlanguage
+
+$twoWeeks = 1209600
+$oneDay = 86400
+INSERT_UPDATE FlexibleSearchRetentionRule; code[unique = true]; searchQuery; retentionTimeSeconds; actionReference ;
+ ; cronJobRule ; "
+SELECT {c:pk}, {c:itemType}
+ FROM {CronJob AS c JOIN ComposedType AS t ON {c:itemtype} = {t:pk} LEFT JOIN Trigger AS trg ON {trg:cronjob} = {c:pk} }
+ WHERE {trg:pk} IS NULL
+ AND {c:code} LIKE '00______%'
+ AND {t:code} IN ( 'ImpExImportCronJob', 'CatalogVersionSyncCronJob', 'SolrIndexerCronJob' )
+ AND {c:endTime} < ?CALC_RETIREMENT_TIME" ; $twoWeeks ; basicRemoveCleanupAction ;
+ ; solrJobRule ; "
+SELECT {j:pk},{j:itemType}
+ FROM {ServicelayerJob AS j LEFT JOIN Trigger AS trg on {trg:job} = {j:pk} }
+ WHERE {trg:pk} IS NULL
+ AND ({j:code} LIKE 'solrIndexerJob_full_%' OR {j:code} LIKE 'solrIndexerJob_update_%')
+ AND {j:modifiedtime} < ?CALC_RETIREMENT_TIME" ; $oneDay ; basicRemoveCleanupAction ;
+INSERT_UPDATE RetentionJob; code[unique = true]; retentionRule(code); batchSize
+ ; cronJobCleanupJob ; cronJobRule ; 1000
+ ; solrJobCleanupJob ; solrJobRule ; 1000
+INSERT_UPDATE CronJob; code[unique = true] ; job(code) ; sessionLanguage(isoCode)[default = $sessionLanguage]
+ ; cronJobCleanupCronJob ; cronJobCleanupJob ;
+ ; solrJobCleanupCronJob ; solrJobCleanupJob ;
+INSERT_UPDATE Trigger; cronJob(code)[unique = true]; cronExpression
+# every day at midnight
+ ; cronJobCleanupCronJob ; 0 0 0 * * ?
+ ; solrJobCleanupCronJob ; 0 0 0 * * ?
diff --git a/resources/impex/sanecleanup/processing/003-cleanup-businessprocess.impex b/resources/impex/sanecleanup/processing/003-cleanup-businessprocess.impex
new file mode 100644
index 0000000..c08cbbf
--- /dev/null
+++ b/resources/impex/sanecleanup/processing/003-cleanup-businessprocess.impex
@@ -0,0 +1,18 @@
+# Import config properties into impex macros
+UPDATE GenericItem[processor=de.hybris.platform.commerceservices.impex.impl.ConfigPropertyImportProcessor];pk[unique=true]
+$sessionLanguage=$config-sanecleanup.jobs.sessionlanguage
+
+$twoWeeks = 1209600
+INSERT_UPDATE FlexibleSearchRetentionRule; code[unique = true] ; searchQuery; retentionTimeSeconds; actionReference ;
+ ; businessProcessRule ; "
+ SELECT {p:pk}, {p:itemtype}
+ FROM {BusinessProcess AS p JOIN ProcessState AS s ON {p:state} = {s:pk} }
+ WHERE {s:code} IN ('SUCCEEDED')
+ AND {p:modifiedTime} < ?CALC_RETIREMENT_TIME" ; $twoWeeks ; basicRemoveCleanupAction ;
+INSERT_UPDATE RetentionJob; code[unique = true] ; retentionRule(code) ; batchSize
+ ; businessProcessCleanupJob ; businessProcessRule ; 1000
+INSERT_UPDATE CronJob; code[unique = true] ; job(code) ; sessionLanguage(isoCode)[default = $sessionLanguage]
+ ; businessProcessCleanupCronJob ; businessProcessCleanupJob ;
+INSERT_UPDATE Trigger; cronJob(code)[unique = true] ; cronExpression
+# every day at midnight
+ ; businessProcessCleanupCronJob ; 0 0 0 * * ?
diff --git a/resources/impex/sanecleanup/ruleengine/001-enable-rules-cleanup.impex b/resources/impex/sanecleanup/ruleengine/001-enable-rules-cleanup.impex
new file mode 100644
index 0000000..4536855
--- /dev/null
+++ b/resources/impex/sanecleanup/ruleengine/001-enable-rules-cleanup.impex
@@ -0,0 +1,7 @@
+# https://help.sap.com/viewer/9d346683b0084da2938be8a285c0c27a/LATEST/en-US/7482a16e3d7c4848a5a7bec7185132c5.html
+
+UPDATE MaintenanceCleanupJob;code[unique=true];active;
+;droolsRulesMaintenanceCleanupPerformable;true
+
+UPDATE Trigger;cronJob(code)[unique=true];active
+;droolsRulesMaintenanceCleanupJob;true
diff --git a/resources/impex/sanecleanup/ruleengine/002-delete-expired-rules.impex b/resources/impex/sanecleanup/ruleengine/002-delete-expired-rules.impex
new file mode 100644
index 0000000..c069c6d
--- /dev/null
+++ b/resources/impex/sanecleanup/ruleengine/002-delete-expired-rules.impex
@@ -0,0 +1,22 @@
+# https://www.sap.com/cxworks/article/538808299/top_10_recommendations_for_improving_the_performance_of_your_commerce_cloud_promotion_engine
+# Tip #7
+
+# Import config properties into impex macros
+UPDATE GenericItem[processor=de.hybris.platform.commerceservices.impex.impl.ConfigPropertyImportProcessor];pk[unique=true]
+$sessionLanguage=$config-sanecleanup.jobs.sessionlanguage
+
+INSERT_UPDATE FlexibleSearchRetentionRule; code[unique = true] ; searchQuery; retentionTimeSeconds; actionReference ;
+; abstractRuleRule ; "
+SELECT {ar:pk}, {ar:itemtype}
+ FROM {AbstractRule AS ar}, {RuleStatus AS rs}
+ WHERE {ar:status} = {rs:pk}
+ AND {rs:code} = 'PUBLISHED'
+ AND {ar:enddate} IS NOT NULL
+ AND {ar:enddate} < ?JAVA_CURRENT_TIME" ; 0 ; basicRemoveCleanupAction ;
+INSERT_UPDATE RetentionJob; code[unique = true] ; retentionRule(code) ; batchSize
+; abstractRuleCleanupJob ; abstractRuleRule ; 1000
+INSERT_UPDATE CronJob; code[unique = true] ; job(code) ; sessionLanguage(isoCode)[default = $sessionLanguage]
+; abstractRuleCleanupCronJob ; abstractRuleCleanupJob ;
+INSERT_UPDATE Trigger; cronJob(code)[unique = true] ; cronExpression
+# every day at midnight
+; abstractRuleCleanupCronJob ; 0 0 0 * * ?
diff --git a/resources/impex/essentialdata-disable-logtodatabase.impex b/resources/impex/sanecleanup/solrfacetsearch/001-disable-logtodatabase.impex
similarity index 100%
rename from resources/impex/essentialdata-disable-logtodatabase.impex
rename to resources/impex/sanecleanup/solrfacetsearch/001-disable-logtodatabase.impex
diff --git a/resources/sanecleanup-spring.xml b/resources/sanecleanup-spring.xml
index 17afe10..e5a9779 100644
--- a/resources/sanecleanup-spring.xml
+++ b/resources/sanecleanup-spring.xml
@@ -5,4 +5,16 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/mpern/sap/cleanup/AfterInitListener.java b/src/mpern/sap/cleanup/AfterInitListener.java
new file mode 100644
index 0000000..bcd9e9c
--- /dev/null
+++ b/src/mpern/sap/cleanup/AfterInitListener.java
@@ -0,0 +1,140 @@
+package mpern.sap.cleanup;
+
+import de.hybris.bootstrap.config.ConfigUtil;
+import de.hybris.bootstrap.config.ExtensionInfo;
+import de.hybris.platform.core.PK;
+import de.hybris.platform.core.Registry;
+import de.hybris.platform.core.model.initialization.SystemSetupAuditModel;
+import de.hybris.platform.servicelayer.event.events.AfterInitializationEndEvent;
+import de.hybris.platform.servicelayer.event.impl.AbstractEventListener;
+import de.hybris.platform.servicelayer.impex.ImportConfig;
+import de.hybris.platform.servicelayer.impex.ImportResult;
+import de.hybris.platform.servicelayer.impex.ImportService;
+import de.hybris.platform.servicelayer.impex.impl.StreamBasedImpExResource;
+import de.hybris.platform.servicelayer.model.ModelService;
+import de.hybris.platform.servicelayer.search.FlexibleSearchQuery;
+import de.hybris.platform.servicelayer.search.FlexibleSearchService;
+import de.hybris.platform.servicelayer.search.SearchResult;
+import de.hybris.platform.servicelayer.user.UserService;
+import de.hybris.platform.tx.Transaction;
+import de.hybris.platform.util.persistence.PersistenceUtils;
+import mpern.sap.cleanup.constants.SanecleanupConstants;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
+
+import java.util.*;
+
+import static org.springframework.core.io.support.ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX;
+
+public class AfterInitListener extends AbstractEventListener {
+
+ private static final Logger LOG = LoggerFactory.getLogger(AfterInitListener.class);
+
+ private final ImportService importService;
+ private final FlexibleSearchService flexibleSearchService;
+ private final ModelService modelService;
+ private final UserService userService;
+ private final PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
+
+
+ public AfterInitListener(ImportService importService, FlexibleSearchService flexibleSearchService, ModelService modelService, UserService userService) {
+ this.importService = importService;
+ this.flexibleSearchService = flexibleSearchService;
+ this.modelService = modelService;
+ this.userService = userService;
+ }
+
+ @Override
+ protected void onEvent(AfterInitializationEndEvent afterInitializationEndEvent) {
+ try {
+ final List extensions = ConfigUtil.getPlatformConfig(Registry.class).getExtensionInfosInBuildOrder();
+ final List applicableImpex = new ArrayList<>();
+ for (ExtensionInfo extension : extensions) {
+ Resource[] resources = resolver.getResources(CLASSPATH_ALL_URL_PREFIX + "/impex/sanecleanup/" + extension.getName() + "/*.impex");
+ List resourceList = Arrays.asList(resources);
+ resourceList.sort(Comparator.comparing(Resource::getFilename));
+ applicableImpex.addAll(resourceList);
+ }
+ Map hashToResource = calculateHashes(applicableImpex);
+ Map filtered = filterAlreadyImported(hashToResource);
+ List auditModels = new ArrayList<>();
+ for (Map.Entry entry : filtered.entrySet()) {
+ Resource resource = entry.getValue();
+ LOG.info("sanecleanup: Importing {}", resource.getFilename());
+ ImportConfig cfg = new ImportConfig();
+ cfg.setEnableCodeExecution(true);
+ cfg.setScript(new StreamBasedImpExResource(resource.getInputStream(), "UTF-8"));
+ ImportResult importResult = importService.importData(cfg);
+ if (importResult.isError()) {
+ LOG.error("sanecleanup: Importing {} FAILED", resource.getFilename());
+ }
+ auditModels.add(generateAuditEntry(entry));
+ }
+ modelService.saveAll(auditModels);
+ removeOldAuditEntries(hashToResource);
+ } catch (Exception e) {
+ LOG.error("sanecleanup - failed", e);
+ }
+ }
+
+ private void removeOldAuditEntries(Map hashToResource) {
+ FlexibleSearchQuery old = new FlexibleSearchQuery("select {pk} from {SystemSetupAudit} where {className} = ?class and {hash} not in (?valid)");
+ old.addQueryParameter("class", AfterInitListener.class.getCanonicalName());
+ old.addQueryParameter("valid", hashToResource.keySet().isEmpty() ? Collections.singleton(PK.NULL_PK) : hashToResource.keySet());
+
+ boolean success = false;
+ try {
+ Transaction.current().begin();
+ success = PersistenceUtils.doWithSLDPersistence(() -> {
+ final SearchResult oldModels = this.flexibleSearchService.search(old);
+ modelService.removeAll(oldModels.getResult());
+ return true;
+ });
+ } finally {
+ if (success) {
+ Transaction.current().commit();
+ } else {
+ Transaction.current().rollback();
+ }
+ }
+ }
+
+ private Map calculateHashes(List suitableImpex) throws Exception {
+ if (suitableImpex.isEmpty()) {
+ return Collections.emptyMap();
+ }
+ Map hashToResource = new LinkedHashMap<>();
+ for (Resource impex : suitableImpex) {
+ // use different hash function to avoid collision with hash calculation for @SystemSetup classes
+ // de.hybris.platform.core.initialization.SystemSetupCollectorResult#computePatchHash
+ String hash = DigestUtils.sha1Hex(impex.getInputStream());
+ hashToResource.put(hash, impex);
+ }
+ return hashToResource;
+ }
+
+ private Map filterAlreadyImported(Map hashResource) {
+ Map filtered = new HashMap<>(hashResource);
+ FlexibleSearchQuery fsq = new FlexibleSearchQuery("select {hash} from {SystemSetupAudit} where {hash} IN (?maybeNew)");
+ fsq.setResultClassList(Collections.singletonList(String.class));
+ fsq.addQueryParameter("maybeNew", hashResource.keySet());
+ final SearchResult search = flexibleSearchService.search(fsq);
+ filtered.keySet().removeAll(search.getResult());
+ return filtered;
+ }
+
+ private SystemSetupAuditModel generateAuditEntry(Map.Entry entry) {
+ final SystemSetupAuditModel audit = modelService.create(SystemSetupAuditModel.class);
+ audit.setHash(entry.getKey());
+ audit.setName(entry.getValue().getFilename());
+ audit.setClassName(AfterInitListener.class.getCanonicalName());
+ audit.setMethodName("onEvent");
+ audit.setRequired(false);
+ audit.setExtensionName(SanecleanupConstants.EXTENSIONNAME);
+ audit.setUser(userService.getCurrentUser());
+ return audit;
+ }
+}
diff --git a/src/mpern/sap/cleanup/cms2/CMSVersionGCPerformable.java b/src/mpern/sap/cleanup/cms2/CMSVersionGCPerformable.java
new file mode 100644
index 0000000..8b7b9ba
--- /dev/null
+++ b/src/mpern/sap/cleanup/cms2/CMSVersionGCPerformable.java
@@ -0,0 +1,181 @@
+package mpern.sap.cleanup.cms2;
+
+import de.hybris.platform.cache.AbstractCacheUnit;
+import de.hybris.platform.cache.Cache;
+import de.hybris.platform.cms2.model.CMSVersionModel;
+import de.hybris.platform.cms2.version.service.CMSVersionGCService;
+import de.hybris.platform.core.PK;
+import de.hybris.platform.cronjob.enums.CronJobResult;
+import de.hybris.platform.cronjob.enums.CronJobStatus;
+import de.hybris.platform.cronjob.model.CronJobModel;
+import de.hybris.platform.servicelayer.config.ConfigurationService;
+import de.hybris.platform.servicelayer.cronjob.AbstractJobPerformable;
+import de.hybris.platform.servicelayer.cronjob.PerformResult;
+import de.hybris.platform.servicelayer.search.FlexibleSearchQuery;
+import de.hybris.platform.servicelayer.search.SearchResult;
+import de.hybris.platform.tx.Transaction;
+import de.hybris.platform.util.FlexibleSearchUtils;
+import de.hybris.platform.util.typesystem.PlatformStringUtils;
+import org.apache.commons.collections4.CollectionUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.jdbc.core.BatchPreparedStatementSetter;
+import org.springframework.jdbc.core.JdbcTemplate;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.*;
+
+//Sane implementation of Content Version GC process.
+//Same logic, without creating Business Process for every run and fast delete using SQL
+//still not optimal because of the potentially huge "IN" clause to filter out valid versions.
+public class CMSVersionGCPerformable extends AbstractJobPerformable {
+
+ private static final Logger LOG = LoggerFactory.getLogger(CMSVersionGCPerformable.class);
+
+ private final ConfigurationService configurationService;
+ private final CMSVersionGCService cmsVersionGCService;
+ private final JdbcTemplate jdbcTemplate;
+
+ public CMSVersionGCPerformable(ConfigurationService configurationService, CMSVersionGCService cmsVersionGCService, JdbcTemplate jdbcTemplate) {
+ this.configurationService = configurationService;
+ this.cmsVersionGCService = cmsVersionGCService;
+ this.jdbcTemplate = jdbcTemplate;
+ }
+
+ @Override
+ public PerformResult perform(CronJobModel cronJobModel) {
+
+ // de.hybris.platform.cms2.version.processengine.action.impl.CollectRetainableCMSVersionsGCProcessAction
+ final List retainableVersions = getRetainableVersions();
+ // de.hybris.platform.cms2.version.processengine.action.impl.CollectRelatedCMSVersionsGCProcessAction
+ Set retainablePKs = collectAllRetainableVersionPKs(retainableVersions);
+
+ if (clearAbortRequestedIfNeeded(cronJobModel)) {
+ return new PerformResult(CronJobResult.UNKNOWN, CronJobStatus.ABORTED);
+ }
+
+ try {
+ // de.hybris.platform.cms2.version.processengine.action.impl.RemoveCMSVersionsGCProcessAction
+ final Optional performResult = deleteObsoleteVersionsInBatches(cronJobModel, retainablePKs);
+ if (performResult.isPresent()) {
+ return performResult.get();
+ }
+ } catch (Exception e) {
+ LOG.error("Processing failed.", e);
+ return new PerformResult(CronJobResult.ERROR, CronJobStatus.UNKNOWN);
+ }
+ return new PerformResult(CronJobResult.SUCCESS, CronJobStatus.FINISHED);
+ }
+
+ private Optional deleteObsoleteVersionsInBatches(CronJobModel cronJobModel, Set retainablePKs) {
+ if (retainablePKs.isEmpty()) {
+ retainablePKs = Collections.singleton(PK.NULL_PK);
+ }
+ FlexibleSearchQuery versionsToDelete = new FlexibleSearchQuery("select {v:pk} from {cmsversion as v} where {v:pk} NOT IN (?retainable) order by {v:pk} desc", Collections.singletonMap("retainable", retainablePKs));
+ versionsToDelete.setResultClassList(Collections.singletonList(PK.class));
+ SearchResult result = flexibleSearchService.search(versionsToDelete);
+
+ if (clearAbortRequestedIfNeeded(cronJobModel)) {
+ return Optional.of(new PerformResult(CronJobResult.UNKNOWN, CronJobStatus.ABORTED));
+ }
+ final List pkList = result.getResult();
+ final int total = pkList.size();
+ int pageSize = 5000;
+ for(int i = 0; i < total; i += pageSize) {
+ int endIdx = i + pageSize;
+ if (endIdx > total) {
+ endIdx = total;
+ }
+ final List batchToDelete = pkList.subList(i, endIdx);
+ boolean success = false;
+ try {
+ Transaction.current().begin();
+ //unfortunately CMSVersion forces jalo because of the needlessly overriden createItem(...) method
+// PersistenceUtils.doWithSLDPersistence(() -> {
+// final Set