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 collect = batchToDelete.stream().map(pk -> modelService.get(pk)).collect(Collectors.toSet()); +// modelService.removeAll(collect); +// modelService.detachAll(); +// return true; +// }); + deleteBatchWithJDBC(batchToDelete); + success = true; + } finally { + if (success) { + Transaction.current().commit(); + } else { + Transaction.current().rollback(); + } + } + if (clearAbortRequestedIfNeeded(cronJobModel)) { + return Optional.of(new PerformResult(CronJobResult.UNKNOWN, CronJobStatus.ABORTED)); + } + } + return Optional.empty(); + } + + private void deleteBatchWithJDBC(final List batchToDelete) { + jdbcTemplate.batchUpdate("delete from cmsversion where pk = ?", new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement preparedStatement, int i) throws SQLException { + preparedStatement.setLong(1, batchToDelete.get(i).getLong()); + } + + @Override + public int getBatchSize() { + return batchToDelete.size(); + } + }); + jdbcTemplate.batchUpdate("delete from cmsversion2cmsversion where sourcepk = ?", new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement preparedStatement, int i) throws SQLException { + preparedStatement.setLong(1, batchToDelete.get(i).getLong()); + } + + @Override + public int getBatchSize() { + return batchToDelete.size(); + } + }); + jdbcTemplate.batchUpdate("delete from cmsversion2cmsversion where targetpk = ?", new BatchPreparedStatementSetter() { + @Override + public void setValues(PreparedStatement preparedStatement, int i) throws SQLException { + preparedStatement.setLong(1, batchToDelete.get(i).getLong()); + } + + @Override + public int getBatchSize() { + return batchToDelete.size(); + } + }); + PK invalidation = batchToDelete.get(0); + // de.hybris.platform.util.Utilities.invalidateCache + Object[] key = new Object[]{Cache.CACHEKEY_HJMP, Cache.CACHEKEY_ENTITY, PlatformStringUtils.valueOf(invalidation.getTypeCode()), invalidation}; + Transaction.current().invalidate(key, 3, AbstractCacheUnit.INVALIDATIONTYPE_REMOVED); + } + + private List getRetainableVersions() { + int maxAgeInDays = configurationService.getConfiguration().getInt("version.gc.maxAgeDays", 0); + int maxNumberVersions = configurationService.getConfiguration().getInt("version.gc.maxNumberVersions", 0); + final List retainableVersions = cmsVersionGCService.getRetainableVersions(maxAgeInDays, maxNumberVersions); + return retainableVersions; + } + + private Set collectAllRetainableVersionPKs(List retainableVersions) { + Set retainablePKs = new HashSet<>(); + for (CMSVersionModel retainableVersion : retainableVersions) { + if (retainableVersion == null) { + continue; + } + retainablePKs.add(retainableVersion.getPk()); + if (CollectionUtils.isNotEmpty(retainableVersion.getRelatedChildren())) { + retainableVersion.getRelatedChildren().stream() + .filter(Objects::nonNull) + .forEach(v -> retainablePKs.add(v.getPk())); + } + } + return retainablePKs; + } + + +}